2021-01-12 09:32:21

Author: Cedric Huchuan Xia (email, github)

Affiliation: Penn Lifespan Informatics and Neuroimaging Center (PennLINC)


This GPS Footprinting project is inspired by Finn et al. Nat Neuro (2015) and Kaufmann et al. Nat Neuro (2015). These authors studied how personal differences in functional brain connectivity can identify individuals, mature during development, alter in neuropsychiatric illness, and differ between genders. The authors referred to these individual differences as brain fingerprinting.

Here, we apply the fingerprinting technique to highly sampled GPS data in a clinical sample of youth. We are interested in how their individual mobility patterns can distinguish one another’s identity, differ between genders, and alter in psychopathological groups. In other words, can our footprint tell us apart and something about our behavior?

GPS data preprocessing was performed according to Ian Barnett’s imputation algorithm, originally published in Biostatistics (2020).

1. Setup Environment

require(ggplot2)
require(summarytools)
require(cowplot)
require(caret)
require(corrplot)
require(RColorBrewer)
require(vembedr)
require(Rmisc)
require(varian)
require(patchwork)
require(plotly)
require(Metrics)
require(dplyr)
require(ggpubr)
require(mosaic)
require(openxlsx)
require(visreg)
require(factoextra)
require(rstatix)
require(gridExtra)
require(colorspace)
require(grid)
source('~/Documents/GitHub/SmartphoneSensorPipeline/Extra/plotting_functions.R')
project_path = "~/Documents/xia_gps/"
data_path = file.path(project_path,"beiwe_output_043020")
gps_df_path = file.path(data_path,"Processed_Data/Group/feature_matrix.txt")

2. GPS Features

The current data has 41 subjects, consisting of 3317 total days, and 15 GPS features. To conserve battery life, a subject’s GPS coordinates were tracked for in a 2-min-on and 18-min-off cycle everyday using their own mobile device via the Beiwe platform. We used Ian Barnett’s algorithm to impute the missing data during the off cycles. Assuming no more data was missing due to various factors, one would generate 144 mins of GPS data per day, or 10% of total minutes in a day.

As you can see from the figure below, we can nicely reconstruct an individuals’ mobility trajectory from these data.
gps_track Figure 1: A weekly view of a subject’s mobility pattern

For an even more intuitive view of the GPS data, take a look at the video here:





From these GPS traces, we extracted daily mobility features, such as max home distance, circadian routine, probability of pauses, as described in Barnett et al., Biostatistics (2020) and defined mathematically in its supplementary material.

To get a flavor of what these features look like, the first six days of GPS data for a subject are attached below.

gps_df = read.table(gps_df_path,header = T, dec = ",", )[,c(1,2,97:111)]

#Define the column types
gps_df$Date = as.Date(gps_df$Date)
gps_df[,3:dim(gps_df)[2]] = apply(gps_df[,3:dim(gps_df)[2]], 2, function(x) as.numeric(x))
head(gps_df)
NA

3. Exclude Data

The first step before further analysis is to exclude data points (days) that had excessive amount of data missing. Here is a historgram of the minutes missing for all 3317 total days.

p=minMiss_histplot(gps_df,200, "All Data")

ggplotly(p)

Figure 2: Minutes missing for all collected days. There are 3317 total days. Dashlines indicate the corresponding percentile.

3a. remove first and last days

First, we will remove the first and last days from each subject, because the application was installed during mid-day at the beginning of the study and uninstalled mid-day at the end of the study.

# loop through each subj to remove 1st and last days of gps data
gps_df_clean = data.frame() #initiate a df
for (subj in unique(gps_df$IID)){ #loop through each subj
  gps_df_subj <- subset(gps_df, IID == subj) #get gps_df per subject
  gps_df_subj <- gps_df_subj[2:(dim(gps_df_subj)[1]-1),] #remove the 1st and last days
  gps_df_clean <- rbind(gps_df_clean,gps_df_subj) #combine all subjs
}

This step removed 82 days. Now, dataset has 41 subjects, consisting of 3235 total days, and 15 GPS features.

Figure 3: Minutes missing after removing the first and last days of each subject. There are 3235 total days. Dashlines indicate the corresponding percentile.

3b. remove days at the sensitivity threshold

Next, we are removing the days with excessive data missingness. This is of course an arbitrary step. Therefore we will conduct a sensitivity analysis of the missingness threshold we set by performing the analysis across multiple different thresholds. For now, the current sensitivity cutoff is set at 1440, which amounts to only excluding those with 100% of GPS missing.

sensitivity_cutoff = 1440 # this controls the cutoff threshold

Warning messages: 1: In readChar(file, size, TRUE) : truncating string with embedded nuls 2: In readChar(file, size, TRUE) : truncating string with embedded nuls

gps_df_clean2 = subset(gps_df_clean, MinsMissing < sensitivity_cutoff)

This step removed 217 days. Now, dataset has 41 subjects, consisting of 3018 total days, and 15 GPS features.

Figure 4: Minutes missing after removing days with all data missing. There are 3018 total days. Dashlines indicate the corresponding percentile.

Figure 5: A Zoom-in plot of minutes missing distribution after removing days with all data missing. There are 3018 total days. Dashlines indicate the corresponding percentile.

subj_minmissing = gps_df_clean2 %>% group_by(IID) %>% summarise(mean = mean(MinsMissing), n = n())

subj_minmissing_plot = ggplot(subj_minmissing) + 
  geom_point(aes(x = reorder(IID, mean), y = (1440-mean)/(1440-1296))) + 
   theme_cowplot() + 
  labs(title = "Data Completeness by Subject", 
  x = "Subjects", y = "Data Completeness") + scale_y_continuous(labels = scales::percent, limits=c(0,1)) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(hjust = 0.5))
ggplotly(subj_minmissing_plot)

4. Random Data Partitions

To operationalize individual footprinting prediction, we randomly split each individual’s available GPS data to two half partitions for 100 times. We used the features from the first half to determine if we can identify the same individual using their data in the second unseen half. Again, to avoid any (un)lucky random splits, we repeated data partition 100 times.

subj_days_covid = gps_df_clean2 %>% group_by(IID) %>% dplyr::tally(name = "days")$days
Error in .$dplyr::tally(name = "days") : 
  3 arguments passed to '$' which requires 2
set.seed(510)
make_subj_seq = function(gps_df_clean2,part_times) {
  subj_seq = list()
  for (subj in unique(gps_df_clean2$IID)){
    subj_data = subset(gps_df_clean2, IID==subj)
    if (dim(subj_data)[1] >5) {
    subj_seq[[subj]] <-createDataPartition(subj_data$IID,times = part_times, p =0.5)
    }
  }
  return(subj_seq)
}
part_times = 100
subj_seq = make_subj_seq(gps_df_clean2, part_times)

5. Build Correlation Matrix

Here, similar to Finn et al. Nat Neuro (2015) and Kaufmann et al. Nat Neuro (2015), we built a Pearson correlation matrix among 15 available GPS features. Below is an illustrative sample of the variables and their correlations.

# an example of subj 1, and first half
example_data = gps_df_clean2[subj_seq$`16xv6ko1`$Resample0001,3:17]
gps_cor = rquery.cormat(example_data, type = "full")

Figure 6: Correlation matrix of GPS features for a subject.

We then calculated the feature matrix for all 41 subjects in the dataset, seperately for each half data partition, and for each random split.

make_feature_matrix = function(gps_df, subj_seq, range) {
  subj_mat_1 = list()
  subj_mat_2 = list()
  for (subj in names(subj_seq)){
    print(subj)
    subj_data = subset(gps_df, IID==subj)
    subj_mat_1[[subj]] = lapply(subj_seq[[subj]], function(list) rquery.cormat(subj_data[list,range], type = "flatten", graph = F)$r)
    subj_mat_2[[subj]] = lapply(subj_seq[[subj]], function(list) rquery.cormat(subj_data[-list,range], type = "flatten", graph = F)$r)
  }
  return(list(subj_mat_1 = subj_mat_1, subj_mat_2 = subj_mat_2 ))
}

gps_clean2_feature = make_feature_matrix(gps_df_clean2, subj_seq, 3:17 )
gps_wide_matrix = data.frame()
subj_days = gps_df_clean2 %>% group_by(IID) %>% dplyr::tally(name = "Days Collected")
for (subj in arrange(subj_days,`Days Collected`)$IID){
  #if ((subj %in% subj_days$IID[which(subj_days$`Days Collected`<=10)]) == T) {
    for (part in 1:5){
      half_1 = gps_clean2_feature$subj_mat_1[[subj]][[part]]$cor
      half_2 = gps_clean2_feature$subj_mat_2[[subj]][[part]]$cor
      half_1_half_2 = c(half_1,half_2)
      gps_wide_matrix = rbind(gps_wide_matrix,half_1)
      gps_wide_matrix = rbind(gps_wide_matrix,half_2)
      }
    #}
}

gps_wide_matrix = t(gps_wide_matrix)
gps_corplot = rquery.cormat(gps_wide_matrix, type = "full",graph=FALSE)
gps_corplot$subj = arrange(subj_days,`Days Collected`)$IID

levelplot(gps_corplot$r,scales=list(draw=FALSE),col.regions = rev(rainbow(1000))[-c(1:20)], region =T, ylab.right = "Pearson correlation", main=list(label='GPS Feature Similarity'),xlab="",ylab="")

6. Match Target

Next we tried to match each target, defined by one subject’s feature matrix in the first half, to a feature matrix in the second half in the same random split. If the same individual’s feature matrix in the second half had the highest correlation among all subjects, then we assigned the match result 1, indicating a succussful match, otherwise 0, indicating an unsccussful match.

# creating a "database" against which target is to be matched with
subj_mat_1 = gps_clean2_feature$subj_mat_1
subj_mat_2 = gps_clean2_feature$subj_mat_2

calc_match_cor = function(subj_mat_1,subj_mat_2) {
  part_times = length(subj_mat_1[[1]])
  database = list() 
  for (time in 1:part_times) {
      database[[time]] = lapply(subj_mat_2, function(subjmat) subjmat[[time]]$cor)
  }
  
    # match target to database
    match_cor = list()
    for (subj1 in names(subj_mat_1)){ #loop through each subj
      # create a list of target across partitions
      target_list = lapply(subj_mat_1[[subj1]], function(part) part$cor)
      # create a match list
      for (time in 1:part_times){
        target_subj_time = target_list[[time]] #loop through each partition
        for (subj2 in names(subj_mat_2)){ #loop everyone in 2nd half
          data_subj_time = subj_mat_2[[subj2]][[time]]$cor
          match_cor[[subj1]][[as.character(time)]][[subj2]] = cor(target_subj_time,data_subj_time,use = "na.or.complete")
        }
      }
    }
  return(match_cor)
}

match_cor = calc_match_cor(subj_mat_1,subj_mat_2)

By tallying up all the successful and unsuccessful matchs in each of the 100 random splits, we calculated a distribution of match accuracy.

calc_acc_time=function(match_cor,method = "max") {
  acc_time = array()
  part_times = length(match_cor[[1]])
  for (time in 1:part_times){
    acc_time[time] = 0
    for (subj in names(subj_mat_1)){
      if (method == "max"){
        position = which.max(unlist(match_cor[[subj]][[as.character(time)]]))
      } 
      else if (method == "min"){
        position = which.min(unlist(match_cor[[subj]][[as.character(time)]]))
      }
      
      predicted_subj = names(subj_mat_1)[position]
      if (predicted_subj == subj) {
        acc_time[time] = acc_time[time] + 1
      }
    }
  }
  acc_time = acc_time/length(names(subj_mat_1))
  return(acc_time)
}
acc_time = calc_acc_time(match_cor, "max")

Over the 100 random splits, the mean match accuracy was 52.93%, with a standard deviation of 5.93%, high of 68.29%, and low of 36.59%, (95%CI: 0.52-0.54).

p_time_cor = hist_chx(acc_time, bins = 16, title = paste("Covariance Features: \n Average Accuracy Across",part_times,"Data Partitions"), xaxis = "Prediction Accuracy", yaxis = "Count")
ggplotly(p_time_cor)

Figure 7: Match accuracy distributon across 100 data splits. Mean match accuracy was 0.53 (95%CI: 0.52-0.54)

calc_acc_subj = function(match_cor, method = "max"){
  acc_subj = array()
  part_times = length(match_cor[[1]])
  for (subj in names(subj_mat_1)){
    acc_subj[subj] = 0
    for (time in 1:part_times){
      if (method == "max"){
        position = which.max(unlist(match_cor[[subj]][[as.character(time)]]))
      } 
      else if (method == "min"){
        position = which.min(unlist(match_cor[[subj]][[as.character(time)]]))
      }
      predicted_subj = names(subj_mat_1)[position]
      if (predicted_subj == subj) {
        acc_subj[subj] = acc_subj[subj] + 1
      }
    }
  }
  acc_subj = acc_subj/part_times
  acc_subj = acc_subj[-1]
  return(acc_subj)
}
acc_subj = calc_acc_subj(match_cor)

In addition to the match accuracy across subjects, we also calculated the match accuracy for each individual. Across the 41 subjects, the mean match accuracy was 37.42%, with a standard deviation of 26.71%, high of 95.8%, and low of 0%.

subj_scatter  = function(gps_df, acc_subj_vector, method){
  subj_df <- data.frame(x=names(subj_seq))
  subj_df$y = acc_subj_vector
  subj_df = subj_df[order(subj_df$y),]
  subj_acc_plot = ggplot(subj_df) + 
  geom_point(aes(x = reorder(x, y), y = y)) + 
   theme_cowplot() + 
    labs(title = paste(method,"Features \n Subject Level Accuracy Across",part_times,"Data Partitions"), 
       x = "Subjects", y = "Prediction Accuracy") +
   theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(hjust = 0.5))
  return(subj_acc_plot)
}

subj_acc_plot = subj_scatter(gps_df_clean2,acc_subj, "Correlated")

ggplotly(subj_scatter(gps_df_clean2,acc_subj, "Correlated"))

Figure 8: Match accuracy distributon across 41 subjects. Mean match accuracy was 37.42%, with a standard deviation of 26.71%, high of 95.8%, and low of 0%. This shows dramatic differences in individual footprinting distinctiveness.

7. Permutation Test

To assess the statistical significance of the results above, we conducted a non-parametric permutation test. To do this, we randomly scrambled pair-wise subject-to-day linkage. We repeated this process 1000 times, and followed the same procedure as above to obtain a null distribution of average match accuracy across sample, and for each individual.

gps_df_perm = gps_df_clean2
perm_time = 1000
perm_acc_time = list()
perm_acc_subj = list()
for (i in 1:perm_time) {
  perm_part_times = 1
  print(paste("processing ...", i,"..."))
  gps_df_perm$IID = sample(gps_df_perm$IID)
  perm_subj_seq = make_subj_seq(gps_df_perm, part_times = perm_part_times)
  perm_gps = make_feature_matrix(gps_df_perm,perm_subj_seq,3:17)
  perm_mat_1 = perm_gps$subj_mat_1
  perm_mat_2 = perm_gps$subj_mat_2
  perm_match_cor = calc_match_cor(perm_mat_1,perm_mat_2)
  perm_acc_time[[i]] = calc_acc_time(perm_match_cor)
  perm_acc_subj[[i]] = calc_acc_subj(perm_match_cor)
}

Over the 1000 permutations, the mean match accuracy was 2.53%, with a standard deviation of 2.51%, high of 400%, and low of 2.5341463%.(95%CI: 0.02-0.03)

perm_acc_time_all = unlist(perm_acc_time)
q_time_cor = hist_chx(perm_acc_time_all, bins = 8, title = paste("Average Accuracy Across",length(perm_acc_time_all),"Permutations"), xaxis = "Prediction Accuracy", yaxis = "Count")
ggplotly(q_time_cor)

Figure 9: Match accuracy distributon across 1000 permutations. Mean match accuracy in permutation was 0.03 (95%CI: 0.02-0.03). This null distribution does not overlapp with the distributon using real data (Fig.7) at all.

We also calculated null distribution of match accuracy for each subject. Over the 1000 permutations, the mean match accuracy was 2.7%, with a standard deviation of 0.81%, high of 4.9%, and low of 1.1%.

perm_subj = list()
for (subj in subj_acc_plot$data$x) {
  perm_subj$val[[subj]] = sapply(perm_acc_subj, function(perm) perm[which(names(perm) == subj)])
  perm_subj$hist[[subj]] = hist_chx(perm_subj[[subj]]$val, bins = 8, title = paste(subj,": Accuracy Across \n",perm_time,"Permutations"), xaxis = "Prediction Accuracy", yaxis = "Count")
  perm_subj$acc[[subj]] = sum(perm_subj$val[[subj]])/perm_time
}
subj_df <- data.frame(x=names(subj_seq))
subj_df$y = acc_subj
subj_df$y_perm = unlist(perm_subj$acc)
subj_df = subj_df[order(subj_df$y),]
p_subj_cor_perm = ggplot(subj_df, aes(x = reorder(x, y), y = value)) + 
  geom_point(aes(y = y, col = "subject data")) + 
  geom_point(aes(y = y_perm, col = "permutation")) +
  theme_cowplot() + 
  labs(title = paste("Cor Features \n Subject Level Accuracy Across",part_times,"Data Partitions"), 
       x = "Subjects", y = "Prediction Accuracy") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(hjust = 0.5))
ggplotly(p_subj_cor_perm)

Figure 8: Match accuracy null distributon across 41 subjects. Mean match accuracy in permutation was 2.7%, with a standard deviation of 0.81%, high of 4.9%, and low of 1.1%. While individuals exhibited marked differences in footprinting distinctivenss, the subject with the lowest prediction accuracy was still statistically significant against the permutation test (i.e. for subject 3e1xnphl: data: 0 % vs. permutation: 1.1%).

8. Mean Features

We calculated the mean of each GPS feature as a metric of feature variability. Similar to above, we calculated mean separately for each data partition and for each individual.

mean_gps = function(gps_df){
  mean_list = sapply(3:17,function(i) mean(gps_df[,i],na.rm=T))
  names(mean_list) = colnames(gps_df)[3:17]
  return(mean_list)
}

make_feature_mean = function(gps_df, subj_seq) {
  subj_mat_1 = list()
  subj_mat_2 = list()
  for (subj in unique(gps_df$IID)){
    subj_data = subset(gps_df_clean2, IID==subj)
    subj_mat_1[[subj]] = lapply(subj_seq[[subj]], function(list) mean_gps(subj_data[list,]))
    subj_mat_2[[subj]] = lapply(subj_seq[[subj]], function(list) mean_gps(subj_data[-list,]))
  }
  return(list(subj_mat_1 = subj_mat_1, subj_mat_2 = subj_mat_2 ))
}

gps_clean2_mean_feat = make_feature_mean(gps_df_clean2, subj_seq)

Figure 9: Feature mean across all subjects. The histogram are for data in the first random half split.

9. Variability Features

We calculated the root mean square of successive differences or RMSSD of each GPS feature as a metric of feature variability. Similar to above, we calculated RMSSD separately for each data partition and for each individual.

rmssd_gps = function(gps_df){
  rmssd_list = sapply(3:17,function(i) rmssd_id(gps_df[,i], gps_df$IID,long=F))
  names(rmssd_list) = colnames(gps_df)[3:17]
  return(rmssd_list)
}

make_feature_rmssd = function(gps_df, subj_seq) {
  subj_mat_1 = list()
  subj_mat_2 = list()
  for (subj in unique(gps_df$IID)){
    subj_data = subset(gps_df_clean2, IID==subj)
    subj_mat_1[[subj]] = lapply(subj_seq[[subj]], function(list) rmssd_gps(subj_data[list,]))
    subj_mat_2[[subj]] = lapply(subj_seq[[subj]], function(list) rmssd_gps(subj_data[-list,]))
  }
  return(list(subj_mat_1 = subj_mat_1, subj_mat_2 = subj_mat_2 ))
}

gps_clean2_rmssd_feat = make_feature_rmssd(gps_df_clean2, subj_seq)

Figure 10: Feature variability for all subjects. Variability is measured by root mean square of successive differences (RMSSD). The histograms are for data in the first random half split.

10. Predict with New Features

calc_match_vector = function(subj_mat_1,subj_mat_2,method) {
  part_times = length(subj_mat_1[[1]])
  database = list() 
  for (time in 1:part_times) {
      database[[time]] = lapply(subj_mat_2, function(subjmat) subjmat[[time]])
  }
  
    # match target to database
    match_cor = list()
    for (subj1 in names(subj_mat_1)){ #loop through each subj
      # create a list of target across partitions
      target_list = lapply(subj_mat_1[[subj1]], function(part) part)
      # create a match list
      for (time in 1:part_times){
        target_subj_time = target_list[[time]] #loop through each partition
        for (subj2 in names(subj_mat_2)){ #loop everyone in 2nd half
          data_subj_time = subj_mat_2[[subj2]][[time]]
          if (method == "cor") {
          match_cor[[subj1]][[as.character(time)]][[subj2]] = cor(target_subj_time,data_subj_time,use = "na.or.complete")
          } 
          else if (method == "rmse") {
            match_cor[[subj1]][[as.character(time)]][[subj2]] = rmse(target_subj_time,data_subj_time)
          }
        }
      }
    }
  return(match_cor)
}
match_mean_rmse = calc_match_vector(gps_clean2_mean_feat$subj_mat_1,gps_clean2_mean_feat$subj_mat_2,"rmse")
acc_time_mean_rmse = calc_acc_time(match_mean_rmse,"min")
acc_subj_mean_rmse = calc_acc_subj(match_mean_rmse,"min")

match_mean_cor = calc_match_vector(gps_clean2_mean_feat$subj_mat_1,gps_clean2_mean_feat$subj_mat_2,"cor")
acc_time_mean_cor = calc_acc_time(match_mean_cor,"max")
acc_subj_mean_cor = calc_acc_subj(match_mean_cor,"max")
match_rmssd_rmse = calc_match_vector(gps_clean2_rmssd_feat$subj_mat_1,gps_clean2_rmssd_feat$subj_mat_2, "rmse")
acc_time_rmssd_rmse = calc_acc_time(match_rmssd_rmse, "min")
acc_subj_rmssd_rmse = calc_acc_subj(match_rmssd_rmse, "min")


match_rmssd_cor = calc_match_vector(gps_clean2_rmssd_feat$subj_mat_1,gps_clean2_rmssd_feat$subj_mat_2, "cor")
acc_time_rmssd_cor = calc_acc_time(match_rmssd_cor, "max")
acc_subj_rmssd_cor = calc_acc_subj(match_rmssd_cor, "max")

11. Permutation Tests with New Features

11a. Average Accuracy

perm_vector = function(gps_df, perm_time, method_feature, method_match){
Warning message:
In readChar(file, size, TRUE) : truncating string with embedded nuls
  gps_df_perm = gps_df
  perm_acc_time = list()
  perm_acc_subj = list()
  for (i in 1:perm_time) {
    perm_part_times = 1
    print(paste("processing ...", i,"..."))
    gps_df_perm$IID = sample(gps_df_perm$IID)
    perm_subj_seq = make_subj_seq(gps_df_perm,part_times = perm_part_times)
    if (method_feature == "rmssd") {
      perm_gps = make_feature_rmssd(gps_df_perm,perm_subj_seq)
    } else if (method_feature == "mean"){
      perm_gps = make_feature_mean(gps_df_perm,perm_subj_seq)
    } else if (method_feature == "combined"){
      perm_gps_rmssd = make_feature_rmssd(gps_df_perm,perm_subj_seq)
      perm_gps_mean = make_feature_mean(gps_df_perm,perm_subj_seq)
      perm_gps_cor = make_feature_matrix(gps_df_perm,perm_subj_seq,3:17)
      subj_mat_1 = list()
      subj_mat_2 = list()
      for (subj in names(perm_gps_cor$subj_mat_1)){
          #print(subj)
          subj_mat_1[[subj]]$Resample1 = as.numeric( c(value(perm_gps_rmssd$subj_mat_1[[subj]][[1]]),value(perm_gps_mean$subj_mat_1[[subj]][[1]]),perm_gps_cor$subj_mat_1[[subj]][[1]]$cor))
          subj_mat_2[[subj]]$Resample1 = as.numeric( c(value(perm_gps_rmssd$subj_mat_2[[subj]][[1]]),value(perm_gps_mean$subj_mat_2[[subj]][[1]]),perm_gps_cor$subj_mat_2[[subj]][[1]]$cor))
      }
      perm_gps = list(subj_mat_1 = subj_mat_1, subj_mat_2 = subj_mat_2)
    }
    perm_mat_1 = perm_gps$subj_mat_1
    perm_mat_2 = perm_gps$subj_mat_2
    if (method_match == "cor") {
      perm_match = calc_match_vector(perm_mat_1,perm_mat_2, "cor")
      perm_acc_time[[i]] = calc_acc_time(perm_match, "max")
      perm_acc_subj[[i]] = calc_acc_subj(perm_match, "max")
    }
    else if (method_match == "rmse"){
      perm_match = calc_match_vector(perm_mat_1,perm_mat_2, "rmse")
      perm_acc_time[[i]] = calc_acc_time(perm_match,"min")
      perm_acc_subj[[i]] = calc_acc_subj(perm_match,"min")
    }
  }
  return(list(perm_acc_time= perm_acc_time,perm_acc_subj = perm_acc_subj))
}
p_time_mean_cor + q_time_mean_cor

Figure 11: Prediction accuracy using mean features and matched by max pearson correlation.

p_time_mean_rmse + q_time_mean_rmse

Figure 11: Prediction accuracy using mean features and matched by min root mean squared.

p_time_rmssd_cor + q_time_rmssd_cor

Figure 12: Prediction accuracy using RMSSD features and matched by max pearson correlation.

p_time_rmssd_rmse + q_time_rmssd_rmse

Figure 13: Prediction accuracy using RMSSD features and matched by min root mean squared.


combine_perm_subj = function(acc_subj_plot, perm_acc_subj) {
  perm_subj = list()
  for (subj in acc_subj_plot$data$x) {
    perm_subj$val[[subj]] = sapply(perm_acc_subj, function(perm) perm[which(names(perm) == subj)])
    perm_subj$hist[[subj]] = hist_chx(perm_subj[[subj]]$val, bins = 8, title = paste(subj,": Accuracy Across \n",perm_time,"Permutations"), xaxis = "Prediction Accuracy", yaxis = "Count")
    perm_subj$acc[[subj]] = sum(perm_subj$val[[subj]])/perm_time
  }
  return(perm_subj)
}


subj_scatter_perm = function(gps_df,acc_subj,perm_subj, method){
    subj_df <- data.frame(x=unique(gps_df$IID))
  subj_df$y = acc_subj
  subj_df$y_perm = unlist(perm_subj$acc)
  subj_df = subj_df[order(subj_df$y),]
  p = ggplot(subj_df, aes(x = reorder(x, y), y = value)) + 
    geom_point(aes(y = y, col = "subject data")) + 
    geom_point(aes(y = y_perm, col = "permutation")) +
    theme_cowplot() + 
    labs(title = paste(method,"Features \n Subject Level Accuracy Across",part_times,"Data Partitions"), 
         x = "Subjects", y = "Prediction Accuracy") +
    theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(hjust = 0.5))
  return(p)
}
p_subj_mean = subj_scatter(gps_df_clean2, acc_subj_mean_cor, "Mean")
perm_subj_mean = combine_perm_subj(p_subj_mean,perm_mean_cor$perm_acc_subj)
p_subj_mean_perm = subj_scatter_perm(gps_df_clean2, acc_subj_mean_cor, perm_subj_mean, "Mean")
ggplotly(p_subj_mean_perm)

Figure 14: Subject level prediction accuracy using mean features and matched by max pearson correlation.

p_subj_rmssd = subj_scatter(gps_df_clean2, acc_subj_rmssd_cor, "RMSSD")
perm_subj_rmssd = combine_perm_subj(p_subj_rmssd,perm_rmssd_cor$perm_acc_subj)
p_subj_rmssd_perm = subj_scatter_perm(gps_df_clean2, acc_subj_rmssd_cor, perm_subj_rmssd, "RMSSD")
ggplotly(p_subj_rmssd_perm)

Figure 15: Subject level prediction accuracy using RMSSD features and matched by max pearson correlation.

12. Compare Features

p_time_cor + p_time_mean + p_time_rmssd
q_time_cor + q_time_mean + q_time_rmssd

Figure 16: Prediction accuracy across three feature sets.

p_subj_cor_perm / p_subj_mean_perm / p_subj_rmssd_perm

Figure 17: Subject level prediction accuracy across three feature sets.

13. Confounding variables

conf_scatter_plot = function(conf_var, acc_subj, col){
  if (!identical(conf_var$IID,names(acc_subj))) {
    stop("subj names do not match")
  }
  conf_df = data.frame(conf = unlist(conf_var[,col]), acc_subj = acc_subj)
  conf_scatter = ggscatter(conf_df, y = "acc_subj", x = "conf",
   add = "reg.line",  # Add regressin line
   add.params = list(color = "black", fill = "lightgray"), # Customize reg. line
   conf.int = TRUE # Add confidence interval
   ) + stat_cor(method = "pearson") +
    theme_cowplot() + theme(plot.title = element_text(hjust = 0.5)) +
    labs(title = paste(col), y = "GPS Footprint \n Prediction Accuracy", x = col)
  return(conf_scatter)
  }

subj_minmissing = gps_df_clean2 %>% group_by(IID) %>% summarise_if(is.numeric, mean, na.rm = TRUE)
sp_plots = list()
for (gps_ft in colnames(subj_minmissing)[-1]){
  sp_plots[[gps_ft]] = conf_scatter_plot(subj_minmissing,acc_subj,gps_ft)
}

sp_plots_pvals = sapply(sp_plots,function(plot) cor.test(plot$data[,1],sp_plots$Hometime$data[,2])$p.value)
sp_plots_pvals_fdr = p.adjust(sp_plots_pvals,method = "fdr")
Reduce(`+`, sp_plots)

Figure 18: Relationships between subject level prediction accuracy and individual features. Notably, there was no relationship between individual data missingness and subject level prediction accuracy.

  acc_subj_perm = acc_subj_perm[sample(length(acc_subj))]
Error: object 'acc_subj_perm' not found

Figure 19: Relationships between subject level prediction accuracy and data quantity.

14. Combine Features

gps_clean2_combined_feat = list()
for (subj in names(subj_mat_1)){
  for (time in 1:part_times){
    cor_feat1 = gps_clean2_feature$subj_mat_1[[subj]][[time]]$cor
    mean_feat1 = gps_clean2_mean_feat$subj_mat_1[[subj]][[time]]
    rmssd_feat1 = gps_clean2_rmssd_feat$subj_mat_1[[subj]][[time]]
    
    cor_feat2 = gps_clean2_feature$subj_mat_2[[subj]][[time]]$cor
    mean_feat2 = gps_clean2_mean_feat$subj_mat_2[[subj]][[time]]
    rmssd_feat2 = gps_clean2_rmssd_feat$subj_mat_2[[subj]][[time]]
    
    gps_clean2_combined_feat$subj_mat_1[[subj]][[time]] = c(cor_feat1, mean_feat1, rmssd_feat1)
    gps_clean2_combined_feat$subj_mat_2[[subj]][[time]] = c(cor_feat2, mean_feat2, rmssd_feat2)
  }
}
match_combined_feat = calc_match_vector(gps_clean2_combined_feat$subj_mat_1, gps_clean2_combined_feat$subj_mat_2, "cor")
acc_time_cb  = calc_acc_time(match_combined_feat, "max")
acc_subj_cb  = calc_acc_subj(match_combined_feat, "max")
p = hist_chx(acc_time_cb, bins = 8, title = paste("Combined Features \n by data partition"), xaxis = "Prediction Accuracy", yaxis = "Count")
q = hist_chx(acc_subj_cb, bins = 8, title = paste("Combined Features \n by subject"), xaxis = "Prediction Accuracy", yaxis = "Count")
p

Figure 20: Prediction accuracy when all three sets of features were combined.

15. Associations with Psychopathology

ids = read.xlsx(file.path(project_path,"data/clinical_data/subjecttracker_4.xlsx"))[1:length(unique(gps_df_clean2$IID)),]
#psych_score = read.csv(file.path(project_path,"data/self_report_scored_20200128.csv"))
# psych_pro = psych %>% filter(ari_proband_complete == 2)
psych_item = read.csv(file.path(project_path,"data/clinical_data/self_report_itemwise.csv"))

psych_beiwe = inner_join(ids,psych_item, by = c("BBLID" = "bblid"))
acc_subj_df = data.frame(beiweID = names(acc_subj), acc = acc_subj)
psych_beiwe_acc_subj = inner_join(acc_subj_df,psych_beiwe, by = "beiweID")


psych_sum = psych_beiwe_acc_subj %>%
            mutate(sum_als = rowSums(.[263:280], na.rm = T)) %>%
            mutate(sum_ari = rowSums(.[grep("^ari_[0-9]$",colnames(psych_beiwe_acc_subj))], na.rm = T))
psych_item

psych_sum = inner_join(psych_sum, subj_days, by = c("beiweID" = "IID"))
psych_sum$days = psych_sum$`Days Collected`
psych_sum = inner_join(subj_minmissing, psych_sum, by = c( "IID" = "beiweID" ))
psych_als = data.frame(als = psych_sum[,263:280], BBLID = psych_sum$BBLID)
psych_ari = data.frame(ari = psych_sum[,23:29], BBLID = psych_sum$BBLID)
  
 # show results

als.pca <- prcomp(na.omit(psych_sum[,263:280]), scale = TRUE)
Error in prcomp.default(na.omit(psych_sum[, 263:280]), scale = TRUE) : 
  cannot rescale a constant/zero column to unit variance
als_ari_sum = lm(acc ~ sum_als + sum_ari + days , data = psych_sum)

#als_fit = lm( acc ~ scale(pca_als) + scale(days) + scale(admin_age) + scale(admin_sex) , data = psych_sum)
#summary(lm( acc ~ scale(als_AD) + scale(days) + scale(admin_age) + scale(admin_sex) , data = psych_sum))$coefficients[2,4]

#ari_fit = lm( acc ~ scale(pca_ari) + scale(days) + scale(admin_age) + scale(admin_sex) , data = psych_sum)
#ari_als_fit = lm( acc ~ scale(pca_als) + scale(pca_ari) + scale(days) + scale(admin_age) + scale(admin_sex) , data = psych_sum)

days_plot = visreg(als_ari_sum, "days", gg = T, line=list(col="black")) + 
            ylab("GPS Footprint \n Prediction Accuracy") + xlab("Days Collected") +
            theme_cowplot() + stat_cor(method = "pearson")
sum_als_plot = visreg(als_ari_sum, "sum_als", gg = T,  line=list(col="black")) + 
            ylab("GPS Footprint \n Prediction Accuracy") + xlab("Affective Lability Score (ALS)") +
            theme_cowplot() + stat_cor(method = "pearson")
sum_ari_plot = visreg(als_ari_sum, "sum_ari", gg = T, line=list(col="black")) + 
            ylab("GPS Footprint \n Prediction Accuracy") + xlab("Affective Reactivity Index (ARI)") +
            theme_cowplot() + stat_cor(method = "pearson")
days_plot / sum_als_plot / sum_ari_plot
  psych_sum_perm$als_perm = psych_sum$sum_als[sample(length(psych_sum$sum_als))]
Error in psych_sum_perm$als_perm = psych_sum$sum_als[sample(length(psych_sum$sum_als))] : 
  object 'psych_sum_perm' not found
mod <- lm(acc~sum_als*sum_ari + days , data=psych_sum)  # just raw scores, no residuals
als_int_plot <- visreg(mod,xvar="sum_als",by="sum_ari",overlay=TRUE,strip.names=TRUE)
visreg(mod,xvar="sum_ari",by="sum_als",overlay=TRUE)

Figure 21: Associations between irritability and prediction accuracy.

16. Associations with Cognition

cnb_score = read.csv(file.path(project_path,"data/grmpy_bifactor_cnb_cleaned.csv"))
cor_traits = read.csv(file.path(project_path,"data/grmpy_corrtraits_cnb_cleaned.csv"))
cnb_beiwe = inner_join(ids,cnb_score, by = c("BBLID" = "bblid"))

17.Similarity Matrix Correlation

gps_wide_matrix_all = data.frame()

subj_seq_mat = arrange(subj_days,`Days Collected`)$IID

for (subj in subj_seq_mat){
  #if ((subj %in% subj_days$IID[which(subj_days$`Days Collected`<=10)]) == T) {
    for (part in 1:1){
      half_1 = gps_clean2_feature$subj_mat_1[[subj]][[part]]$cor
      half_2 = gps_clean2_feature$subj_mat_2[[subj]][[part]]$cor
      half_1_half_2 = c(half_1,half_2)
      gps_wide_matrix_all = rbind(gps_wide_matrix_all,half_1_half_2)
      }
    #}
}

gps_wide_matrix_all = t(gps_wide_matrix_all)
gps_corplot_all = rquery.cormat(gps_wide_matrix_all, type = "full",graph=FALSE)
gps_corplot_all$subj = arrange(subj_days,`Days Collected`)$IID

levelplot(gps_corplot_all$r,scales=list(draw=FALSE),col.regions = rev(rainbow(1000))[-c(1:20)], region =T, ylab.right = "Pearson correlation", main=list(label='GPS Feature Similarity'),xlab="Subjects",ylab="Subjects")
n_subj = length(subj_seq_mat)
gps_days_sim_matrix = matrix(NA, n_subj,n_subj)
for (i in 1:n_subj){
  for (j in 1:n_subj){
    subj_i_days = subj_days$`Days Collected`[which(subj_days$IID == subj_seq_mat[i])]
    subj_j_days = subj_days$`Days Collected`[which(subj_days$IID == subj_seq_mat[j])]
    gps_days_sim_matrix[i,j] = abs(subj_i_days-subj_j_days)/max(subj_days$`Days Collected`)
  }
}
levelplot(gps_days_sim_matrix,scales=list(draw=FALSE),col.regions = rev(rainbow(1000))[-c(1:20)], region =T, ylab.right = "Differences in Days Collected", main=list(label='Days Collected Similarity'),xlab="Subjects",ylab="Subjects")
n_subj = length(subj_seq_mat)
gps_mins_sim_matrix = matrix(NA, n_subj,n_subj)
for (i in 1:n_subj){
  for (j in 1:n_subj){
    subj_i_mins = subj_minmissing$MinsMissing[which(subj_minmissing$IID == subj_seq_mat[i])]
    subj_j_mins = subj_minmissing$MinsMissing[which(subj_minmissing$IID == subj_seq_mat[j])]
    gps_mins_sim_matrix[i,j] = abs(subj_i_mins-subj_j_mins)/max(subj_minmissing$MinsMissing)
  }
}
levelplot(gps_mins_sim_matrix,scales=list(draw=FALSE),col.regions = rev(rainbow(1000))[-c(1:20)], region =T, ylab.right = "Differences in Days Collected", main=list(label='Mins Missing Similarity'),xlab="Subjects",ylab="Subjects")
gps_sim_df = data.frame(gps = gps_corplot_all$r[lower.tri(gps_corplot_all$r)], days = gps_days_sim_matrix[lower.tri(gps_days_sim_matrix)], mins = gps_mins_sim_matrix[lower.tri(gps_mins_sim_matrix)])

days_sim_plot = ggscatter(gps_sim_df, y = "gps", x = "days",
   add = "reg.line",  # Add regressin line
   add.params = list(color = "black", fill = "lightgray"), # Customize reg. line
   conf.int = TRUE # Add confidence interval
   ) + stat_cor(method = "pearson") +
    theme_cowplot() + theme(plot.title = element_text(hjust = 0.5)) +
    labs(y = "GPS Similarity", x = "Days Collected Similarity", title = "Similarity Correlation")

mins_sim_plot = ggscatter(gps_sim_df, y = "gps", x = "mins",
   add = "reg.line",  # Add regressin line
   add.params = list(color = "black", fill = "lightgray"), # Customize reg. line
   conf.int = TRUE # Add confidence interval
   ) + stat_cor(method = "pearson") +
    theme_cowplot() + theme(plot.title = element_text(hjust = 0.5)) +
    labs(y = "GPS Similarity", x = "Mins Missing Similarity", title = "Similarity Correlation")

days_sim_plot / mins_sim_plot

18.GPS Score

gps_mean = gps_df_clean2 %>% group_by(IID) %>% summarise_if(is.numeric, mean, na.rm = TRUE)
res.pca <- prcomp(gps_mean[,2:16], scale = TRUE)
fviz_eig(res.pca)
gps_score = res.pca$x[,'PC1']
gps_score_df = data.frame(beiweID = gps_mean$IID, gps_score = gps_score)
fc_com_mean_1 = as.data.frame(sapply(fc_com_1, function(com) sapply(com, function(subj) mean(subj$V1))))
fc_com_mean_1$BBLID = as.numeric(rownames(fc_com_mean_1))
fc_com_mean_1 = inner_join(ids[,c(1,3)],fc_com_mean_1, by = "BBLID" )
fc_com_mean_1_gps = inner_join(fc_com_mean_1,gps_score_df, by = "beiweID")


fviz_pca_ind(res.pca,
             col.ind = "cos2", # Color by the quality of representation
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE     # Avoid text overlapping
             )

fviz_pca_var(res.pca,
             col.var = "contrib", # Color by contributions to the PC
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE     # Avoid text overlapping
             )

fviz_pca_biplot(res.pca, repel = TRUE,
                col.var = "#2E9FDF", # Variables color
                col.ind = "#696969"  # Individuals color
                )

19.Feature Leison

feat_names = names(gps_df_clean2[,3:17])
subj_seq_100 = make_subj_seq(gps_df_clean2, 100)
gps_ft_leison = list()
gps_df_perm = gps_df_clean2
perm_time_cut = 100
perm_acc_time_cut = list()
perm_acc_subj_cut = list()
for (i in 1:15){
  ft_rmv = feat_names[i]
  print(ft_rmv)
  range = c(3:17)[-i]
  print(range)
  # original data
  feature_nd = make_feature_matrix(gps_df_clean2,subj_seq_100,range)
  match_cor_nd = calc_match_cor(feature_nd$subj_mat_1,feature_nd$subj_mat_2)
  gps_ft_leison[[ft_rmv]]$acc_time = calc_acc_time(match_cor_nd, "max")
  gps_ft_leison[[ft_rmv]]$acc_subj = calc_acc_subj(match_cor_nd, "max")
  print(mean(gps_ft_leison[[ft_rmv]]$acc_time))
}
  #permutation data
for (i in 1:15){
  ft_rmv = feat_names[i]
  print(ft_rmv)
  range = c(3:17)[-i]
  print(range)
  for (j in 1:perm_time_cut) {
    perm_part_times = 1
    print(paste("permuting ...", i,"..."))
    gps_df_perm$IID = sample(gps_df_perm$IID)
    perm_subj_seq = make_subj_seq(gps_df_perm, part_times = perm_part_times)
    perm_gps = make_feature_matrix(gps_df_perm,perm_subj_seq,range)
    perm_mat_1 = perm_gps$subj_mat_1
    perm_mat_2 = perm_gps$subj_mat_2
    perm_match_cor = calc_match_cor(perm_mat_1,perm_mat_2)
    perm_acc_time_cut[[ft_rmv]][[j]] = calc_acc_time(perm_match_cor)
    perm_acc_subj_cut[[ft_rmv]][[j]] = calc_acc_subj(perm_match_cor)
  }
  print(mean(unlist(perm_acc_time_cut[[ft_rmv]])))
}
acc_time_lesion_df$`None_Removed` = acc_time
Error in `$<-.data.frame`(`*tmp*`, None_Removed, value = c(0.48780487804878,  : 
  replacement has 1000 rows, data has 10
acc_time_lesion_perm_df <- as.data.frame(gather(acc_time_lesion_df, feature, acc_time, colnames(acc_time_lesion_df), factor_key=TRUE))
attributes are not identical across measure variables;
they will be dropped
subj_mins = gps_df_clean2 %>% group_by(IID) %>% dplyr::tally(name = "Days Collected")
conf_scatter_plot(subj_minmissing, gps_ft_leison$MinsMissing$acc_subj, "MinsMissing")

19. Misc Info

#sessionInfo()
LS0tCnRpdGxlOiAiR1BTIEZvb3RwcmludGluZyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBpbmNsdWRlczoKICAgICAgYWZ0ZXJfYm9keTogZm9vdGVyLmh0bWwKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6CiAgICAgIHRvY19jb2xsYXBzZWQ6IHllcwotLS0KYHIgU3lzLnRpbWUoKWAKCkF1dGhvcjogW0NlZHJpYyBIdWNodWFuIFhpYV0oaHR0cHM6Ly93d3cucGVubmxpbmMuaW8vdGVhbS9DZWRyaWMtSHVjaHVhbi1YaWEpIChbZW1haWxdKGh4aWFAdXBlbm4uZWR1KSwgW2dpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL2NlZHJpY3gvKSkKCkFmZmlsaWF0aW9uOiBQZW5uIExpZmVzcGFuIEluZm9ybWF0aWNzIGFuZCBOZXVyb2ltYWdpbmcgQ2VudGVyIChbUGVubkxJTkNdKHBlbm5saW5jLmlvKSkgCgoqKioKCgpUaGlzICpHUFMgRm9vdHByaW50aW5nIHByb2plY3QqIGlzIGluc3BpcmVkIGJ5IFtGaW5uIGV0IGFsLiBOYXQgTmV1cm8gICgyMDE1KV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubi40MTM1KSBhbmQgW0thdWZtYW5uIGV0IGFsLiBOYXQgTmV1cm8gKDIwMTUpXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5My0wMTktMDQ3MS03KS4gVGhlc2UgYXV0aG9ycyBzdHVkaWVkIGhvdyBwZXJzb25hbCBkaWZmZXJlbmNlcyBpbiBmdW5jdGlvbmFsIGJyYWluIGNvbm5lY3Rpdml0eSBjYW4gaWRlbnRpZnkgaW5kaXZpZHVhbHMsIG1hdHVyZSBkdXJpbmcgZGV2ZWxvcG1lbnQsIGFsdGVyIGluIG5ldXJvcHN5Y2hpYXRyaWMgaWxsbmVzcywgYW5kIGRpZmZlciBiZXR3ZWVuIGdlbmRlcnMuIFRoZSBhdXRob3JzIHJlZmVycmVkIHRvIHRoZXNlIGluZGl2aWR1YWwgZGlmZmVyZW5jZXMgYXMgKmJyYWluIGZpbmdlcnByaW50aW5nKi4KCj4gSGVyZSwgd2UgYXBwbHkgdGhlIGZpbmdlcnByaW50aW5nIHRlY2huaXF1ZSB0byBoaWdobHkgc2FtcGxlZCBHUFMgZGF0YSBpbiBhIGNsaW5pY2FsIHNhbXBsZSBvZiB5b3V0aC4gV2UgYXJlIGludGVyZXN0ZWQgaW4gaG93IHRoZWlyIGluZGl2aWR1YWwgbW9iaWxpdHkgcGF0dGVybnMgY2FuIGRpc3Rpbmd1aXNoIG9uZSBhbm90aGVyJ3MgaWRlbnRpdHksIGRpZmZlciBiZXR3ZWVuIGdlbmRlcnMsIGFuZCBhbHRlciBpbiBwc3ljaG9wYXRob2xvZ2ljYWwgZ3JvdXBzLiBJbiBvdGhlciB3b3JkcywgY2FuIG91ciBmb290cHJpbnQgdGVsbCB1cyBhcGFydCBhbmQgc29tZXRoaW5nIGFib3V0IG91ciBiZWhhdmlvcj8gCgpHUFMgZGF0YSBwcmVwcm9jZXNzaW5nIHdhcyBwZXJmb3JtZWQgYWNjb3JkaW5nIHRvIFtJYW4gQmFybmV0dCdzIGltcHV0YXRpb24gYWxnb3JpdGhtXShodHRwczovL2dpdGh1Yi5jb20vaWFuamFtZXNiYXJuZXR0L1NtYXJ0cGhvbmVTZW5zb3JQaXBlbGluZSksIG9yaWdpbmFsbHkgcHVibGlzaGVkIGluIFtCaW9zdGF0aXN0aWNzICgyMDIwKV0oaHR0cHM6Ly9hY2FkZW1pYy5vdXAuY29tL2Jpb3N0YXRpc3RpY3MvYXJ0aWNsZS1hYnN0cmFjdC8yMS8yL2U5OC81MTQ1OTA4KS4KCiMjIyAxLiBTZXR1cCBFbnZpcm9ubWVudCAKYGBge3IgbG9hZCBsaWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0V9CnJlcXVpcmUoZ2dwbG90MikKcmVxdWlyZShzdW1tYXJ5dG9vbHMpCnJlcXVpcmUoY293cGxvdCkKcmVxdWlyZShjYXJldCkKcmVxdWlyZShjb3JycGxvdCkKcmVxdWlyZShSQ29sb3JCcmV3ZXIpCnJlcXVpcmUodmVtYmVkcikKcmVxdWlyZShSbWlzYykKcmVxdWlyZSh2YXJpYW4pCnJlcXVpcmUocGF0Y2h3b3JrKQpyZXF1aXJlKHBsb3RseSkKcmVxdWlyZShNZXRyaWNzKQpyZXF1aXJlKGRwbHlyKQpyZXF1aXJlKGdncHVicikKcmVxdWlyZShtb3NhaWMpCnJlcXVpcmUob3Blbnhsc3gpCnJlcXVpcmUodmlzcmVnKQpyZXF1aXJlKGZhY3RvZXh0cmEpCnJlcXVpcmUocnN0YXRpeCkKcmVxdWlyZShncmlkRXh0cmEpCnJlcXVpcmUoY29sb3JzcGFjZSkKcmVxdWlyZShncmlkKQpzb3VyY2UoJ34vRG9jdW1lbnRzL0dpdEh1Yi9TbWFydHBob25lU2Vuc29yUGlwZWxpbmUvRXh0cmEvcGxvdHRpbmdfZnVuY3Rpb25zLlInKQpgYGAKCgpgYGB7ciBkZWZpbmUgcGF0aHN9CnByb2plY3RfcGF0aCA9ICJ+L0RvY3VtZW50cy94aWFfZ3BzLyIKZGF0YV9wYXRoID0gZmlsZS5wYXRoKHByb2plY3RfcGF0aCwiYmVpd2Vfb3V0cHV0XzA0MzAyMCIpCmdwc19kZl9wYXRoID0gZmlsZS5wYXRoKGRhdGFfcGF0aCwiUHJvY2Vzc2VkX0RhdGEvR3JvdXAvZmVhdHVyZV9tYXRyaXgudHh0IikKYGBgCgojIyMgMi4gR1BTIEZlYXR1cmVzClRoZSBjdXJyZW50IGRhdGEgaGFzIGByIGxlbmd0aCh1bmlxdWUoZ3BzX2RmJElJRCkpYCBzdWJqZWN0cywgY29uc2lzdGluZyBvZiBgciBkaW0oZ3BzX2RmKVsxXWAgdG90YWwgZGF5cywgYW5kIGByIGxlbmd0aChjb2xuYW1lcyhncHNfZGYpKS0yYCBHUFMgZmVhdHVyZXMuIFRvIGNvbnNlcnZlIGJhdHRlcnkgbGlmZSwgYSBzdWJqZWN0J3MgR1BTIGNvb3JkaW5hdGVzIHdlcmUgdHJhY2tlZCBmb3IgaW4gYSAyLW1pbi1vbiBhbmQgMTgtbWluLW9mZiBjeWNsZSBldmVyeWRheSB1c2luZyB0aGVpciBvd24gbW9iaWxlIGRldmljZSB2aWEgdGhlIFtCZWl3ZSBwbGF0Zm9ybV0oaHR0cHM6Ly93d3cuYmVpd2Uub3JnKS4gV2UgdXNlZCBbSWFuIEJhcm5ldHQncyBhbGdvcml0aG1dKGh0dHBzOi8vZ2l0aHViLmNvbS9pYW5qYW1lc2Jhcm5ldHQvU21hcnRwaG9uZVNlbnNvclBpcGVsaW5lKSB0byBpbXB1dGUgdGhlIG1pc3NpbmcgZGF0YSBkdXJpbmcgdGhlIG9mZiBjeWNsZXMuIEFzc3VtaW5nIG5vIG1vcmUgZGF0YSB3YXMgbWlzc2luZyBkdWUgdG8gdmFyaW91cyBmYWN0b3JzLCBvbmUgd291bGQgZ2VuZXJhdGUgYHIgMiozKjI0YCBtaW5zIG9mIEdQUyBkYXRhIHBlciBkYXksIG9yIGByIDIvMjAqMTAwYCUgb2YgdG90YWwgbWludXRlcyBpbiBhIGRheS4gCgpBcyB5b3UgY2FuIHNlZSBmcm9tIHRoZSBmaWd1cmUgYmVsb3csIHdlIGNhbiBuaWNlbHkgcmVjb25zdHJ1Y3QgYW4gaW5kaXZpZHVhbHMnIG1vYmlsaXR5IHRyYWplY3RvcnkgZnJvbSB0aGVzZSBkYXRhLgo8Y2VudGVyPgohW2dwc190cmFja10oLi9ncHNfdHJhY2tfc2FtcGxlLnBuZykKKipGaWd1cmUgMTogQSB3ZWVrbHkgdmlldyBvZiBhIHN1YmplY3QncyBtb2JpbGl0eSBwYXR0ZXJuKioKPC9jZW50ZXI+CgpGb3IgYW4gZXZlbiBtb3JlIGludHVpdGl2ZSB2aWV3IG9mIHRoZSBHUFMgZGF0YSwgdGFrZSBhIGxvb2sgYXQgdGhlIHZpZGVvIGhlcmU6Cgo8Y2VudGVyPgpgYGB7ciBlY2hvPUZBTFNFfQplbWJlZF91cmwoImh0dHBzOi8veW91dHUuYmUvS29iRVNndGZvT28iKQpgYGAKPC9jZW50ZXI+CgoKIDxicj48YnI+PGJyPjxicj4KCkZyb20gdGhlc2UgR1BTIHRyYWNlcywgd2UgZXh0cmFjdGVkIGRhaWx5IG1vYmlsaXR5IGZlYXR1cmVzLCBzdWNoIGFzICptYXggaG9tZSBkaXN0YW5jZSosICpjaXJjYWRpYW4gcm91dGluZSosICpwcm9iYWJpbGl0eSBvZiBwYXVzZXMqLCBhcyBkZXNjcmliZWQgaW4gW0Jhcm5ldHQgZXQgYWwuLCBCaW9zdGF0aXN0aWNzICgyMDIwKV0oaHR0cHM6Ly9hY2FkZW1pYy5vdXAuY29tL2Jpb3N0YXRpc3RpY3MvYXJ0aWNsZS1hYnN0cmFjdC8yMS8yL2U5OC81MTQ1OTA4KSBhbmQgZGVmaW5lZCBtYXRoZW1hdGljYWxseSBpbiBpdHMgW3N1cHBsZW1lbnRhcnkgbWF0ZXJpYWxdKGh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS9vcGVuP2lkPTFqSEZKTFhqVVN3b3Nlck41dFB0QS0taDlHRWNWZloxMCkuCgpUbyBnZXQgYSBmbGF2b3Igb2Ygd2hhdCB0aGVzZSBmZWF0dXJlcyBsb29rIGxpa2UsIHRoZSBmaXJzdCBzaXggZGF5cyBvZiBHUFMgZGF0YSBmb3IgYSBzdWJqZWN0IGFyZSBhdHRhY2hlZCBiZWxvdy4gCmBgYHtyIHJlYWRfZ3BzfQpncHNfZGYgPSByZWFkLnRhYmxlKGdwc19kZl9wYXRoLGhlYWRlciA9IFQsIGRlYyA9ICIsIiwgKVssYygxLDIsOTc6MTExKV0KCiNEZWZpbmUgdGhlIGNvbHVtbiB0eXBlcwpncHNfZGYkRGF0ZSA9IGFzLkRhdGUoZ3BzX2RmJERhdGUpCmdwc19kZlssMzpkaW0oZ3BzX2RmKVsyXV0gPSBhcHBseShncHNfZGZbLDM6ZGltKGdwc19kZilbMl1dLCAyLCBmdW5jdGlvbih4KSBhcy5udW1lcmljKHgpKQpoZWFkKGdwc19kZikKCmBgYAoKIyMjIDMuIEV4Y2x1ZGUgRGF0YQpUaGUgZmlyc3Qgc3RlcCBiZWZvcmUgZnVydGhlciBhbmFseXNpcyBpcyB0byBleGNsdWRlIGRhdGEgcG9pbnRzIChkYXlzKSB0aGF0IGhhZCBleGNlc3NpdmUgYW1vdW50IG9mIGRhdGEgbWlzc2luZy4gSGVyZSBpcyBhIGhpc3RvcmdyYW0gb2YgdGhlIG1pbnV0ZXMgbWlzc2luZyBmb3IgYWxsIGByIGRpbShncHNfZGYpWzFdYCB0b3RhbCBkYXlzLiAKCmBgYHtyIGVjaG89VFJVRSwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnA9bWluTWlzc19oaXN0cGxvdChncHNfZGYsMjAwLCAiQWxsIERhdGEiKQoKZ2dwbG90bHkocCkKYGBgCioqRmlndXJlIDI6IE1pbnV0ZXMgbWlzc2luZyBmb3IgYWxsIGNvbGxlY3RlZCBkYXlzLioqIFRoZXJlIGFyZSBgciBkaW0oZ3BzX2RmKVsxXWAgdG90YWwgZGF5cy4gRGFzaGxpbmVzIGluZGljYXRlIHRoZSBjb3JyZXNwb25kaW5nIHBlcmNlbnRpbGUuCgoKCgojIyMjIDNhLiAgcmVtb3ZlIGZpcnN0IGFuZCBsYXN0IGRheXMKRmlyc3QsIHdlIHdpbGwgcmVtb3ZlIHRoZSBmaXJzdCBhbmQgbGFzdCBkYXlzIGZyb20gZWFjaCBzdWJqZWN0LCBiZWNhdXNlIHRoZSBhcHBsaWNhdGlvbiB3YXMgaW5zdGFsbGVkIGR1cmluZyBtaWQtZGF5IGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhlIHN0dWR5IGFuZCB1bmluc3RhbGxlZCBtaWQtZGF5IGF0IHRoZSBlbmQgb2YgdGhlIHN0dWR5LiAKCmBgYHtyIDFzdF9sYXN0X2RheXN9CiMgbG9vcCB0aHJvdWdoIGVhY2ggc3ViaiB0byByZW1vdmUgMXN0IGFuZCBsYXN0IGRheXMgb2YgZ3BzIGRhdGEKZ3BzX2RmX2NsZWFuID0gZGF0YS5mcmFtZSgpICNpbml0aWF0ZSBhIGRmCmZvciAoc3ViaiBpbiB1bmlxdWUoZ3BzX2RmJElJRCkpeyAjbG9vcCB0aHJvdWdoIGVhY2ggc3ViagogIGdwc19kZl9zdWJqIDwtIHN1YnNldChncHNfZGYsIElJRCA9PSBzdWJqKSAjZ2V0IGdwc19kZiBwZXIgc3ViamVjdAogIGdwc19kZl9zdWJqIDwtIGdwc19kZl9zdWJqWzI6KGRpbShncHNfZGZfc3ViailbMV0tMSksXSAjcmVtb3ZlIHRoZSAxc3QgYW5kIGxhc3QgZGF5cwogIGdwc19kZl9jbGVhbiA8LSByYmluZChncHNfZGZfY2xlYW4sZ3BzX2RmX3N1YmopICNjb21iaW5lIGFsbCBzdWJqcwp9CmBgYAoKVGhpcyBzdGVwIHJlbW92ZWQgYHIgZGltKGdwc19kZilbMV0gLSBkaW0oZ3BzX2RmX2NsZWFuKVsxXWAgZGF5cy4gTm93LCBkYXRhc2V0IGhhcyBgciBsZW5ndGgodW5pcXVlKGdwc19kZl9jbGVhbiRJSUQpKWAgc3ViamVjdHMsIGNvbnNpc3Rpbmcgb2YgYHIgZGltKGdwc19kZl9jbGVhbilbMV1gIHRvdGFsIGRheXMsIGFuZCBgciBsZW5ndGgoY29sbmFtZXMoZ3BzX2RmX2NsZWFuKSktMmAgR1BTIGZlYXR1cmVzLgoKYGBge3IgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTQsIGVjaG89RkFMU0V9CnA9bWluTWlzc19oaXN0cGxvdChncHNfZGZfY2xlYW4sMjAwLCAiQWZ0ZXIgUmVtb3ZpbmcgMXN0IGFuZCBMYXN0IERheXMiKQpnZ3Bsb3RseShwKQpgYGAKKipGaWd1cmUgMzogTWludXRlcyBtaXNzaW5nIGFmdGVyIHJlbW92aW5nIHRoZSBmaXJzdCBhbmQgbGFzdCBkYXlzIG9mIGVhY2ggc3ViamVjdC4qKiBUaGVyZSBhcmUgYHIgZGltKGdwc19kZl9jbGVhbilbMV1gIHRvdGFsIGRheXMuIERhc2hsaW5lcyBpbmRpY2F0ZSB0aGUgY29ycmVzcG9uZGluZyBwZXJjZW50aWxlLgoKIyMjIyAzYi4gIHJlbW92ZSBkYXlzIGF0IHRoZSBzZW5zaXRpdml0eSB0aHJlc2hvbGQKTmV4dCwgd2UgYXJlIHJlbW92aW5nIHRoZSBkYXlzIHdpdGggZXhjZXNzaXZlIGRhdGEgbWlzc2luZ25lc3MuIFRoaXMgaXMgb2YgY291cnNlIGFuIGFyYml0cmFyeSBzdGVwLiBUaGVyZWZvcmUgd2Ugd2lsbCBjb25kdWN0IGEgc2Vuc2l0aXZpdHkgYW5hbHlzaXMgb2YgdGhlIG1pc3NpbmduZXNzIHRocmVzaG9sZCB3ZSBzZXQgYnkgcGVyZm9ybWluZyB0aGUgYW5hbHlzaXMgYWNyb3NzIG11bHRpcGxlIGRpZmZlcmVudCB0aHJlc2hvbGRzLiBGb3Igbm93LCB0aGUgY3VycmVudCBzZW5zaXRpdml0eSBjdXRvZmYgaXMgc2V0IGF0IGByIHNlbnNpdGl2aXR5X2N1dG9mZmAsIHdoaWNoIGFtb3VudHMgdG8gb25seSBleGNsdWRpbmcgdGhvc2Ugd2l0aCBgciBzZW5zaXRpdml0eV9jdXRvZmYvMTQ0MCoxMDBgJSBvZiBHUFMgbWlzc2luZy4KCmBgYHtyIHJlc3VsdHM9J2FzaXMnfQpzZW5zaXRpdml0eV9jdXRvZmYgPSAxNDQwICMgdGhpcyBjb250cm9scyB0aGUgY3V0b2ZmIHRocmVzaG9sZApncHNfZGZfY2xlYW4yID0gc3Vic2V0KGdwc19kZl9jbGVhbiwgTWluc01pc3NpbmcgPCBzZW5zaXRpdml0eV9jdXRvZmYpCmBgYAoKVGhpcyBzdGVwIHJlbW92ZWQgYHIgZGltKGdwc19kZl9jbGVhbilbMV0gLSBkaW0oZ3BzX2RmX2NsZWFuMilbMV1gIGRheXMuIE5vdywgZGF0YXNldCBoYXMgYHIgbGVuZ3RoKHVuaXF1ZShncHNfZGZfY2xlYW4yJElJRCkpYCBzdWJqZWN0cywgY29uc2lzdGluZyBvZiBgciBkaW0oZ3BzX2RmX2NsZWFuMilbMV1gIHRvdGFsIGRheXMsIGFuZCBgciBsZW5ndGgoY29sbmFtZXMoZ3BzX2RmX2NsZWFuMikpLTJgIEdQUyBmZWF0dXJlcy4KCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00LCBlY2hvPUZBTFNFfQpwID0gbWluTWlzc19oaXN0cGxvdChncHNfZGZfY2xlYW4yLDIwMCwgcGFzdGUoIkFmdGVyIFJlbW92aW5nIE1pc3NpbmcgR3JlYXRlciB0aGFuICIsc2Vuc2l0aXZpdHlfY3V0b2ZmKSkKZ2dwbG90bHkocCkKYGBgCioqRmlndXJlIDQ6IE1pbnV0ZXMgbWlzc2luZyBhZnRlciByZW1vdmluZyBkYXlzIHdpdGggYWxsIGRhdGEgbWlzc2luZy4qKiBUaGVyZSBhcmUgYHIgZGltKGdwc19kZl9jbGVhbjIpWzFdYCB0b3RhbCBkYXlzLiBEYXNobGluZXMgaW5kaWNhdGUgdGhlIGNvcnJlc3BvbmRpbmcgcGVyY2VudGlsZS4KCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NCwgZWNobz1GQUxTRX0KcCA9IG1pbk1pc3NfaGlzdHBsb3Qoc3Vic2V0KGdwc19kZl9jbGVhbjIsTWluc01pc3Npbmc+PTEyOTYpLDIwMCwgIlpvb20gSW4gUGxvdCIpCmdncGxvdGx5KHApCmBgYAoqKkZpZ3VyZSA1OiBBIFpvb20taW4gcGxvdCBvZiBtaW51dGVzIG1pc3NpbmcgZGlzdHJpYnV0aW9uIGFmdGVyIHJlbW92aW5nIGRheXMgd2l0aCBhbGwgZGF0YSBtaXNzaW5nLioqIFRoZXJlIGFyZSBgciBkaW0oZ3BzX2RmX2NsZWFuMilbMV1gIHRvdGFsIGRheXMuIERhc2hsaW5lcyBpbmRpY2F0ZSB0aGUgY29ycmVzcG9uZGluZyBwZXJjZW50aWxlLgoKYGBge3IgaW52ZXN0aWdhdGUgaW5kaXZpZHVhbCBkYXRhIHF1YWxpdHksIGZpZy5hbGlnbj0iY2VudGVyIiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc3Vial9taW5taXNzaW5nID0gZ3BzX2RmX2NsZWFuMiAlPiUgZ3JvdXBfYnkoSUlEKSAlPiUgc3VtbWFyaXNlKG1lYW4gPSBtZWFuKE1pbnNNaXNzaW5nKSwgbiA9IG4oKSkKCnN1YmpfbWlubWlzc2luZ19wbG90ID0gZ2dwbG90KHN1YmpfbWlubWlzc2luZykgKyAKICBnZW9tX3BvaW50KGFlcyh4ID0gcmVvcmRlcihJSUQsIG1lYW4pLCB5ID0gKDE0NDAtbWVhbikvKDE0NDAtMTI5NikpKSArIAogICB0aGVtZV9jb3dwbG90KCkgKyAKICBsYWJzKHRpdGxlID0gIkRhdGEgQ29tcGxldGVuZXNzIGJ5IFN1YmplY3QiLCAKICB4ID0gIlN1YmplY3RzIiwgeSA9ICJEYXRhIENvbXBsZXRlbmVzcyIpICsgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwgbGltaXRzPWMoMCwxKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSwgc2l6ZSA9IDgpLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKZ2dwbG90bHkoc3Vial9taW5taXNzaW5nX3Bsb3QpCmBgYAoKCgoKCgojIyMgNC4gUmFuZG9tIERhdGEgUGFydGl0aW9ucwoKVG8gb3BlcmF0aW9uYWxpemUgaW5kaXZpZHVhbCBmb290cHJpbnRpbmcgcHJlZGljdGlvbiwgd2UgcmFuZG9tbHkgc3BsaXQgZWFjaCBpbmRpdmlkdWFsJ3MgYXZhaWxhYmxlIEdQUyBkYXRhIHRvIHR3byBoYWxmIHBhcnRpdGlvbnMgZm9yIGByIHBhcnRfdGltZXNgIHRpbWVzLiBXZSB1c2VkIHRoZSBmZWF0dXJlcyBmcm9tIHRoZSBmaXJzdCBoYWxmIHRvIGRldGVybWluZSBpZiB3ZSBjYW4gaWRlbnRpZnkgdGhlIHNhbWUgaW5kaXZpZHVhbCB1c2luZyB0aGVpciBkYXRhIGluIHRoZSBzZWNvbmQgdW5zZWVuIGhhbGYuIEFnYWluLCB0byBhdm9pZCBhbnkgKHVuKWx1Y2t5IHJhbmRvbSBzcGxpdHMsIHdlIHJlcGVhdGVkIGRhdGEgcGFydGl0aW9uIGByIHBhcnRfdGltZXNgIHRpbWVzLgoKCmBgYHtyIENPVklEIGFuYWx5c2lzfQpncHNfZGZfY2xlYW4yX2NvdmlkID0gZ3BzX2RmX2NsZWFuMltncHNfZGZfY2xlYW4yJERhdGUgPiBhcy5EYXRlKCIyMDIwLTMtMTAiKSxdCmdwc19kZl9jbGVhbjJfMzEwMjAgPSBncHNfZGZfY2xlYW4yW2dwc19kZl9jbGVhbjIkRGF0ZSA8PSBhcy5EYXRlKCIyMDIwLTMtMTAiKSxdCgpncHNfZGZfY2xlYW4yID0gZ3BzX2RmX2NsZWFuMl8zMTAyMAoKYCVub3RpbiVgIDwtIE5lZ2F0ZShgJWluJWApCmdwc19kZl9jbGVhbjJfb3RoZXIgPSBzdWJzZXQoZ3BzX2RmX2NsZWFuMiwgSUlEICVub3RpbiUgdW5pcXVlKGdwc19kZl9jbGVhbjJfY292aWQkSUlEKSkKCmdwc19kZl9jbGVhbjJfY292aWRfYWxsID0gcmJpbmQoZ3BzX2RmX2NsZWFuMl9vdGhlcixncHNfZGZfY2xlYW4yX2NvdmlkKQoKYWNjX3N1YmpfMzEwMjAgIAphY2NfdGltZV8zMTAyMCAKCmFjY19zdWJqXzMyMzIwIAphY2NfdGltZV8zMjMyMCAgCgphY2Nfc3Vial9hbGwgIAphY2NfdGltZV9hbGwgCgphY2Nfc3Vial9jb3ZpZCA9IGFjY19zdWJqCmFjY190aW1lX2NvdmlkID0gYWNjX3RpbWUKCnBsb3QoYWNjX3N1YmpfYWxsLGFjY19zdWJqXzMxMDIwKSArYWJsaW5lKGNvZWYgPSBjKDAsMSkpICsgYWJsaW5lKGNvZWYgPSBjKDAuOTMsMCkpICsgYWJsaW5lKHYgPSAwLjg4MikgKyBhYmxpbmUoaCA9MC42MikgKyBhYmxpbmUodiA9MC44MjQpICsgYWJsaW5lKGggPTAuMDUpICsgYWJsaW5lKHYgPTAuODA4KQoKcGxvdChhY2Nfc3Vial9hbGwsYWNjX3N1YmpfY292aWQpICsgYWJsaW5lKGNvZWYgPSBjKDAsMSkpIApzdWJqX2RheXNfYWxsID0gZ3BzX2RmX2NsZWFuMiAlPiUgZ3JvdXBfYnkoSUlEKSAlPiUgZHBseXI6OnRhbGx5KG5hbWUgPSAiZGF5cyIpCnN1YmpfZGF5c19jb3ZpZCA9IGdwc19kZl9jbGVhbjJfY292aWRfYWxsICU+JSBncm91cF9ieShJSUQpICU+JSBkcGx5cjo6dGFsbHkobmFtZSA9ICJkYXlzIikKc3Vial9kYXlzXzMxMDIwID0gZ3BzX2RmX2NsZWFuMl8zMTAyMCAlPiUgZ3JvdXBfYnkoSUlEKSAlPiUgZHBseXI6OnRhbGx5KG5hbWUgPSAiZGF5cyIpCgpgYGAKCgpgYGB7cn0Kc2V0LnNlZWQoNTEwKQptYWtlX3N1Ympfc2VxID0gZnVuY3Rpb24oZ3BzX2RmX2NsZWFuMixwYXJ0X3RpbWVzKSB7CiAgc3Vial9zZXEgPSBsaXN0KCkKICBmb3IgKHN1YmogaW4gdW5pcXVlKGdwc19kZl9jbGVhbjIkSUlEKSl7CiAgICBzdWJqX2RhdGEgPSBzdWJzZXQoZ3BzX2RmX2NsZWFuMiwgSUlEPT1zdWJqKQogICAgaWYgKGRpbShzdWJqX2RhdGEpWzFdID41KSB7CiAgICBzdWJqX3NlcVtbc3Vial1dIDwtY3JlYXRlRGF0YVBhcnRpdGlvbihzdWJqX2RhdGEkSUlELHRpbWVzID0gcGFydF90aW1lcywgcCA9MC41KQogICAgfQogIH0KICByZXR1cm4oc3Vial9zZXEpCn0KcGFydF90aW1lcyA9IDEwMApzdWJqX3NlcSA9IG1ha2Vfc3Vial9zZXEoZ3BzX2RmX2NsZWFuMiwgcGFydF90aW1lcykKCmBgYAoKCgoKCgojIyMgNS4gQnVpbGQgQ29ycmVsYXRpb24gTWF0cml4CgpIZXJlLCBzaW1pbGFyIHRvIFtGaW5uIGV0IGFsLiBOYXQgTmV1cm8gICgyMDE1KV0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubi40MTM1KSBhbmQgW0thdWZtYW5uIGV0IGFsLiBOYXQgTmV1cm8gKDIwMTUpXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTU5My0wMTktMDQ3MS03KSwgd2UgYnVpbHQgYSBQZWFyc29uIGNvcnJlbGF0aW9uIG1hdHJpeCBhbW9uZyBgciBsZW5ndGgoY29sbmFtZXMoZ3BzX2RmX2NsZWFuMikpLTJgIGF2YWlsYWJsZSBHUFMgZmVhdHVyZXMuIEJlbG93IGlzIGFuIGlsbHVzdHJhdGl2ZSBzYW1wbGUgb2YgdGhlIHZhcmlhYmxlcyBhbmQgdGhlaXIgY29ycmVsYXRpb25zLgoKYGBge3IgZXhhbXBsZV9jb3JfZmlnLCBmaWcud2lkdGg9MywgZmlnLndpZHRoPTMsIGZpZy5hbGlnbj0iY2VudGVyIn0KIyBhbiBleGFtcGxlIG9mIHN1YmogMSwgYW5kIGZpcnN0IGhhbGYKZXhhbXBsZV9kYXRhID0gZ3BzX2RmX2NsZWFuMltzdWJqX3NlcSRgMTZ4djZrbzFgJFJlc2FtcGxlMDAwMSwzOjE3XQpncHNfY29yID0gcnF1ZXJ5LmNvcm1hdChleGFtcGxlX2RhdGEsIHR5cGUgPSAiZnVsbCIpCgpgYGAKKipGaWd1cmUgNjogQ29ycmVsYXRpb24gbWF0cml4IG9mIEdQUyBmZWF0dXJlcyBmb3IgYSBzdWJqZWN0LioqCgoKV2UgdGhlbiBjYWxjdWxhdGVkIHRoZSBmZWF0dXJlIG1hdHJpeCBmb3IgYWxsIGByIGxlbmd0aCh1bmlxdWUoZ3BzX2RmX2NsZWFuMiRJSUQpKWAgc3ViamVjdHMgaW4gdGhlIGRhdGFzZXQsIHNlcGVyYXRlbHkgZm9yIGVhY2ggaGFsZiBkYXRhIHBhcnRpdGlvbiwgYW5kIGZvciBlYWNoIHJhbmRvbSBzcGxpdC4gCgpgYGB7ciBjcmVhdGUgZmVhdHVyZSBtYXRyaXggZm9yIGV2ZXJ5b25lLCB3YXJuaW5nPUZBTFNFfQptYWtlX2ZlYXR1cmVfbWF0cml4ID0gZnVuY3Rpb24oZ3BzX2RmLCBzdWJqX3NlcSwgcmFuZ2UpIHsKICBzdWJqX21hdF8xID0gbGlzdCgpCiAgc3Vial9tYXRfMiA9IGxpc3QoKQogIGZvciAoc3ViaiBpbiBuYW1lcyhzdWJqX3NlcSkpewogICAgcHJpbnQoc3ViaikKICAgIHN1YmpfZGF0YSA9IHN1YnNldChncHNfZGYsIElJRD09c3ViaikKICAgIHN1YmpfbWF0XzFbW3N1YmpdXSA9IGxhcHBseShzdWJqX3NlcVtbc3Vial1dLCBmdW5jdGlvbihsaXN0KSBycXVlcnkuY29ybWF0KHN1YmpfZGF0YVtsaXN0LHJhbmdlXSwgdHlwZSA9ICJmbGF0dGVuIiwgZ3JhcGggPSBGKSRyKQogICAgc3Vial9tYXRfMltbc3Vial1dID0gbGFwcGx5KHN1Ympfc2VxW1tzdWJqXV0sIGZ1bmN0aW9uKGxpc3QpIHJxdWVyeS5jb3JtYXQoc3Vial9kYXRhWy1saXN0LHJhbmdlXSwgdHlwZSA9ICJmbGF0dGVuIiwgZ3JhcGggPSBGKSRyKQogIH0KICByZXR1cm4obGlzdChzdWJqX21hdF8xID0gc3Vial9tYXRfMSwgc3Vial9tYXRfMiA9IHN1YmpfbWF0XzIgKSkKfQoKZ3BzX2NsZWFuMl9mZWF0dXJlID0gbWFrZV9mZWF0dXJlX21hdHJpeChncHNfZGZfY2xlYW4yLCBzdWJqX3NlcSwgMzoxNyApCmBgYAoKYGBge3IgdmlzdWFsaXphdGlvbiBvZiBmZWF0dXJlIGNvcnJlbGF0aW9ucyBhY3Jvc3Mgc3ViamVjdHN9Cmdwc193aWRlX21hdHJpeCA9IGRhdGEuZnJhbWUoKQpzdWJqX2RheXMgPSBncHNfZGZfY2xlYW4yICU+JSBncm91cF9ieShJSUQpICU+JSBkcGx5cjo6dGFsbHkobmFtZSA9ICJEYXlzIENvbGxlY3RlZCIpCmZvciAoc3ViaiBpbiBhcnJhbmdlKHN1YmpfZGF5cyxgRGF5cyBDb2xsZWN0ZWRgKSRJSUQpewogICNpZiAoKHN1YmogJWluJSBzdWJqX2RheXMkSUlEW3doaWNoKHN1YmpfZGF5cyRgRGF5cyBDb2xsZWN0ZWRgPD0xMCldKSA9PSBUKSB7CiAgICBmb3IgKHBhcnQgaW4gMTo1KXsKICAgICAgaGFsZl8xID0gZ3BzX2NsZWFuMl9mZWF0dXJlJHN1YmpfbWF0XzFbW3N1YmpdXVtbcGFydF1dJGNvcgogICAgICBoYWxmXzIgPSBncHNfY2xlYW4yX2ZlYXR1cmUkc3Vial9tYXRfMltbc3Vial1dW1twYXJ0XV0kY29yCiAgICAgIGhhbGZfMV9oYWxmXzIgPSBjKGhhbGZfMSxoYWxmXzIpCiAgICAgIGdwc193aWRlX21hdHJpeCA9IHJiaW5kKGdwc193aWRlX21hdHJpeCxoYWxmXzEpCiAgICAgIGdwc193aWRlX21hdHJpeCA9IHJiaW5kKGdwc193aWRlX21hdHJpeCxoYWxmXzIpCiAgICAgIH0KICAgICN9Cn0KCmdwc193aWRlX21hdHJpeCA9IHQoZ3BzX3dpZGVfbWF0cml4KQpncHNfY29ycGxvdCA9IHJxdWVyeS5jb3JtYXQoZ3BzX3dpZGVfbWF0cml4LCB0eXBlID0gImZ1bGwiLGdyYXBoPUZBTFNFKQpncHNfY29ycGxvdCRzdWJqID0gYXJyYW5nZShzdWJqX2RheXMsYERheXMgQ29sbGVjdGVkYCkkSUlECgpsZXZlbHBsb3QoZ3BzX2NvcnBsb3QkcixzY2FsZXM9bGlzdChkcmF3PUZBTFNFKSxjb2wucmVnaW9ucyA9IHJldihyYWluYm93KDEwMDApKVstYygxOjIwKV0sIHJlZ2lvbiA9VCwgeWxhYi5yaWdodCA9ICJQZWFyc29uIGNvcnJlbGF0aW9uIiwgbWFpbj1saXN0KGxhYmVsPSdHUFMgRmVhdHVyZSBTaW1pbGFyaXR5JykseGxhYj0iIix5bGFiPSIiKQpgYGAKCiMjIyA2LiBNYXRjaCBUYXJnZXQKCk5leHQgd2UgdHJpZWQgdG8gbWF0Y2ggZWFjaCB0YXJnZXQsIGRlZmluZWQgYnkgb25lIHN1YmplY3QncyBmZWF0dXJlIG1hdHJpeCBpbiB0aGUgZmlyc3QgaGFsZiwgdG8gYSBmZWF0dXJlIG1hdHJpeCBpbiB0aGUgc2Vjb25kIGhhbGYgaW4gdGhlIHNhbWUgcmFuZG9tIHNwbGl0LiBJZiB0aGUgc2FtZSBpbmRpdmlkdWFsJ3MgZmVhdHVyZSBtYXRyaXggaW4gdGhlIHNlY29uZCBoYWxmIGhhZCB0aGUgaGlnaGVzdCBjb3JyZWxhdGlvbiBhbW9uZyBhbGwgc3ViamVjdHMsIHRoZW4gd2UgYXNzaWduZWQgdGhlIG1hdGNoIHJlc3VsdCAxLCBpbmRpY2F0aW5nIGEgc3VjY3Vzc2Z1bCBtYXRjaCwgb3RoZXJ3aXNlIDAsIGluZGljYXRpbmcgYW4gdW5zY2N1c3NmdWwgbWF0Y2guCgpgYGB7ciBjYWxjIG1hdGNoIGNvcn0KIyBjcmVhdGluZyBhICJkYXRhYmFzZSIgYWdhaW5zdCB3aGljaCB0YXJnZXQgaXMgdG8gYmUgbWF0Y2hlZCB3aXRoCnN1YmpfbWF0XzEgPSBncHNfY2xlYW4yX2ZlYXR1cmUkc3Vial9tYXRfMQpzdWJqX21hdF8yID0gZ3BzX2NsZWFuMl9mZWF0dXJlJHN1YmpfbWF0XzIKCmNhbGNfbWF0Y2hfY29yID0gZnVuY3Rpb24oc3Vial9tYXRfMSxzdWJqX21hdF8yKSB7CiAgcGFydF90aW1lcyA9IGxlbmd0aChzdWJqX21hdF8xW1sxXV0pCiAgZGF0YWJhc2UgPSBsaXN0KCkgCiAgZm9yICh0aW1lIGluIDE6cGFydF90aW1lcykgewogICAgICBkYXRhYmFzZVtbdGltZV1dID0gbGFwcGx5KHN1YmpfbWF0XzIsIGZ1bmN0aW9uKHN1YmptYXQpIHN1YmptYXRbW3RpbWVdXSRjb3IpCiAgfQogIAogICAgIyBtYXRjaCB0YXJnZXQgdG8gZGF0YWJhc2UKICAgIG1hdGNoX2NvciA9IGxpc3QoKQogICAgZm9yIChzdWJqMSBpbiBuYW1lcyhzdWJqX21hdF8xKSl7ICNsb29wIHRocm91Z2ggZWFjaCBzdWJqCiAgICAgICMgY3JlYXRlIGEgbGlzdCBvZiB0YXJnZXQgYWNyb3NzIHBhcnRpdGlvbnMKICAgICAgdGFyZ2V0X2xpc3QgPSBsYXBwbHkoc3Vial9tYXRfMVtbc3ViajFdXSwgZnVuY3Rpb24ocGFydCkgcGFydCRjb3IpCiAgICAgICMgY3JlYXRlIGEgbWF0Y2ggbGlzdAogICAgICBmb3IgKHRpbWUgaW4gMTpwYXJ0X3RpbWVzKXsKICAgICAgICB0YXJnZXRfc3Vial90aW1lID0gdGFyZ2V0X2xpc3RbW3RpbWVdXSAjbG9vcCB0aHJvdWdoIGVhY2ggcGFydGl0aW9uCiAgICAgICAgZm9yIChzdWJqMiBpbiBuYW1lcyhzdWJqX21hdF8yKSl7ICNsb29wIGV2ZXJ5b25lIGluIDJuZCBoYWxmCiAgICAgICAgICBkYXRhX3N1YmpfdGltZSA9IHN1YmpfbWF0XzJbW3N1YmoyXV1bW3RpbWVdXSRjb3IKICAgICAgICAgIG1hdGNoX2Nvcltbc3ViajFdXVtbYXMuY2hhcmFjdGVyKHRpbWUpXV1bW3N1YmoyXV0gPSBjb3IodGFyZ2V0X3N1YmpfdGltZSxkYXRhX3N1YmpfdGltZSx1c2UgPSAibmEub3IuY29tcGxldGUiKQogICAgICAgIH0KICAgICAgfQogICAgfQogIHJldHVybihtYXRjaF9jb3IpCn0KCm1hdGNoX2NvciA9IGNhbGNfbWF0Y2hfY29yKHN1YmpfbWF0XzEsc3Vial9tYXRfMikKCmBgYAoKCkJ5IHRhbGx5aW5nIHVwIGFsbCB0aGUgc3VjY2Vzc2Z1bCBhbmQgdW5zdWNjZXNzZnVsIG1hdGNocyBpbiBlYWNoIG9mIHRoZSBgciBwYXJ0X3RpbWVzYCByYW5kb20gc3BsaXRzLCB3ZSBjYWxjdWxhdGVkIGEgZGlzdHJpYnV0aW9uIG9mIG1hdGNoIGFjY3VyYWN5LgoKYGBge3IgY2FsYyBhY2N1cmFjeSBieSBwYXJ0aXRpb259CmNhbGNfYWNjX3RpbWU9ZnVuY3Rpb24obWF0Y2hfY29yLG1ldGhvZCA9ICJtYXgiKSB7CiAgYWNjX3RpbWUgPSBhcnJheSgpCiAgcGFydF90aW1lcyA9IGxlbmd0aChtYXRjaF9jb3JbWzFdXSkKICBmb3IgKHRpbWUgaW4gMTpwYXJ0X3RpbWVzKXsKICAgIGFjY190aW1lW3RpbWVdID0gMAogICAgZm9yIChzdWJqIGluIG5hbWVzKHN1YmpfbWF0XzEpKXsKICAgICAgaWYgKG1ldGhvZCA9PSAibWF4Iil7CiAgICAgICAgcG9zaXRpb24gPSB3aGljaC5tYXgodW5saXN0KG1hdGNoX2Nvcltbc3Vial1dW1thcy5jaGFyYWN0ZXIodGltZSldXSkpCiAgICAgIH0gCiAgICAgIGVsc2UgaWYgKG1ldGhvZCA9PSAibWluIil7CiAgICAgICAgcG9zaXRpb24gPSB3aGljaC5taW4odW5saXN0KG1hdGNoX2Nvcltbc3Vial1dW1thcy5jaGFyYWN0ZXIodGltZSldXSkpCiAgICAgIH0KICAgICAgCiAgICAgIHByZWRpY3RlZF9zdWJqID0gbmFtZXMoc3Vial9tYXRfMSlbcG9zaXRpb25dCiAgICAgIGlmIChwcmVkaWN0ZWRfc3ViaiA9PSBzdWJqKSB7CiAgICAgICAgYWNjX3RpbWVbdGltZV0gPSBhY2NfdGltZVt0aW1lXSArIDEKICAgICAgfQogICAgfQogIH0KICBhY2NfdGltZSA9IGFjY190aW1lL2xlbmd0aChuYW1lcyhzdWJqX21hdF8xKSkKICByZXR1cm4oYWNjX3RpbWUpCn0KYWNjX3RpbWUgPSBjYWxjX2FjY190aW1lKG1hdGNoX2NvciwgIm1heCIpCmBgYAoKT3ZlciB0aGUgYHIgcGFydF90aW1lc2AgcmFuZG9tIHNwbGl0cywgdGhlIG1lYW4gbWF0Y2ggYWNjdXJhY3kgd2FzIGByIHJvdW5kKG1lYW4oYWNjX3RpbWUpLDQpKjEwMGAlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGByIHJvdW5kKHNkKGFjY190aW1lKSw0KSoxMDBgJSwgaGlnaCBvZiBgciByb3VuZChtYXgoYWNjX3RpbWUpLDQpKjEwMGAlLCBhbmQgbG93IG9mIGByIHJvdW5kKG1pbihhY2NfdGltZSksNCkqMTAwYCUsICg5NSVDSTogYHIgcm91bmQoQ0koYWNjX3RpbWUpWydsb3dlciddLDIpYC1gciByb3VuZChDSShhY2NfdGltZSlbJ3VwcGVyJ10sMilgKS4KCmBgYHtyIHBsb3QgYWNjdXJhY3kgYnkgcGFydGl0aW9uIGhpc3RvZ3JhbSwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KcF90aW1lX2NvciA9IGhpc3RfY2h4KGFjY190aW1lLCBiaW5zID0gMTYsIHRpdGxlID0gcGFzdGUoIkNvdmFyaWFuY2UgRmVhdHVyZXM6IFxuIEF2ZXJhZ2UgQWNjdXJhY3kgQWNyb3NzIixwYXJ0X3RpbWVzLCJEYXRhIFBhcnRpdGlvbnMiKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKZ2dwbG90bHkocF90aW1lX2NvcikKYGBgCioqRmlndXJlIDc6IE1hdGNoIGFjY3VyYWN5IGRpc3RyaWJ1dG9uIGFjcm9zcyBgciBwYXJ0X3RpbWVzYCBkYXRhIHNwbGl0cy4qKiBNZWFuIG1hdGNoIGFjY3VyYWN5IHdhcyBgciByb3VuZChDSShhY2NfdGltZSlbJ21lYW4nXSwyKWAgKDk1JUNJOiBgciByb3VuZChDSShhY2NfdGltZSlbJ2xvd2VyJ10sMilgLWByIHJvdW5kKENJKGFjY190aW1lKVsndXBwZXInXSwyKWApCgoKYGBge3IgY2FsYyBhY2N1cmFjeSBieSBzdWJqfQpjYWxjX2FjY19zdWJqID0gZnVuY3Rpb24obWF0Y2hfY29yLCBtZXRob2QgPSAibWF4Iil7CiAgYWNjX3N1YmogPSBhcnJheSgpCiAgcGFydF90aW1lcyA9IGxlbmd0aChtYXRjaF9jb3JbWzFdXSkKICBmb3IgKHN1YmogaW4gbmFtZXMoc3Vial9tYXRfMSkpewogICAgYWNjX3N1Ympbc3Vial0gPSAwCiAgICBmb3IgKHRpbWUgaW4gMTpwYXJ0X3RpbWVzKXsKICAgICAgaWYgKG1ldGhvZCA9PSAibWF4Iil7CiAgICAgICAgcG9zaXRpb24gPSB3aGljaC5tYXgodW5saXN0KG1hdGNoX2Nvcltbc3Vial1dW1thcy5jaGFyYWN0ZXIodGltZSldXSkpCiAgICAgIH0gCiAgICAgIGVsc2UgaWYgKG1ldGhvZCA9PSAibWluIil7CiAgICAgICAgcG9zaXRpb24gPSB3aGljaC5taW4odW5saXN0KG1hdGNoX2Nvcltbc3Vial1dW1thcy5jaGFyYWN0ZXIodGltZSldXSkpCiAgICAgIH0KICAgICAgcHJlZGljdGVkX3N1YmogPSBuYW1lcyhzdWJqX21hdF8xKVtwb3NpdGlvbl0KICAgICAgaWYgKHByZWRpY3RlZF9zdWJqID09IHN1YmopIHsKICAgICAgICBhY2Nfc3VialtzdWJqXSA9IGFjY19zdWJqW3N1YmpdICsgMQogICAgICB9CiAgICB9CiAgfQogIGFjY19zdWJqID0gYWNjX3N1YmovcGFydF90aW1lcwogIGFjY19zdWJqID0gYWNjX3N1YmpbLTFdCiAgcmV0dXJuKGFjY19zdWJqKQp9CmFjY19zdWJqID0gY2FsY19hY2Nfc3ViaihtYXRjaF9jb3IpCmBgYAoKCkluIGFkZGl0aW9uIHRvIHRoZSBtYXRjaCBhY2N1cmFjeSBhY3Jvc3Mgc3ViamVjdHMsIHdlIGFsc28gY2FsY3VsYXRlZCB0aGUgbWF0Y2ggYWNjdXJhY3kgZm9yIGVhY2ggaW5kaXZpZHVhbC4gQWNyb3NzIHRoZSBgciBsZW5ndGgodW5pcXVlKGdwc19kZl9jbGVhbjIkSUlEKSlgIHN1YmplY3RzLCB0aGUgbWVhbiBtYXRjaCBhY2N1cmFjeSB3YXMgYHIgcm91bmQobWVhbihzdWJqX2RmJHkpLDQpKjEwMGAlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGByIHJvdW5kKHNkKHN1YmpfZGYkeSksNCkqMTAwYCUsIGhpZ2ggb2YgYHIgcm91bmQobWF4KHN1YmpfZGYkeSksNCkqMTAwYCUsIGFuZCBsb3cgb2YgYHIgcm91bmQobWluKHN1YmpfZGYkeSksNCkqMTAwYCUuCgpgYGB7ciBwbG90IGFjY3VyYWN5IGJ5IHN1YmplY3RzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpzdWJqX3NjYXR0ZXIgID0gZnVuY3Rpb24oZ3BzX2RmLCBhY2Nfc3Vial92ZWN0b3IsIG1ldGhvZCl7CiAgc3Vial9kZiA8LSBkYXRhLmZyYW1lKHg9bmFtZXMoc3Vial9zZXEpKQogIHN1YmpfZGYkeSA9IGFjY19zdWJqX3ZlY3RvcgogIHN1YmpfZGYgPSBzdWJqX2RmW29yZGVyKHN1YmpfZGYkeSksXQogIHN1YmpfYWNjX3Bsb3QgPSBnZ3Bsb3Qoc3Vial9kZikgKyAKICBnZW9tX3BvaW50KGFlcyh4ID0gcmVvcmRlcih4LCB5KSwgeSA9IHkpKSArIAogICB0aGVtZV9jb3dwbG90KCkgKyAKICAgIGxhYnModGl0bGUgPSBwYXN0ZShtZXRob2QsIkZlYXR1cmVzIFxuIFN1YmplY3QgTGV2ZWwgQWNjdXJhY3kgQWNyb3NzIixwYXJ0X3RpbWVzLCJEYXRhIFBhcnRpdGlvbnMiKSwgCiAgICAgICB4ID0gIlN1YmplY3RzIiwgeSA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IikgKwogICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEsIHNpemUgPSA4KSwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgcmV0dXJuKHN1YmpfYWNjX3Bsb3QpCn0KCnN1YmpfYWNjX3Bsb3QgPSBzdWJqX3NjYXR0ZXIoZ3BzX2RmX2NsZWFuMixhY2Nfc3ViaiwgIkNvcnJlbGF0ZWQiKQoKZ2dwbG90bHkoc3Vial9zY2F0dGVyKGdwc19kZl9jbGVhbjIsYWNjX3N1YmosICJDb3JyZWxhdGVkIikpCmBgYAoqKkZpZ3VyZSA4OiBNYXRjaCBhY2N1cmFjeSBkaXN0cmlidXRvbiBhY3Jvc3MgYHIgbGVuZ3RoKHVuaXF1ZShncHNfZGZfY2xlYW4yJElJRCkpYCBzdWJqZWN0cy4qKiBNZWFuIG1hdGNoIGFjY3VyYWN5IHdhcyBgciByb3VuZChtZWFuKHN1YmpfZGYkeSksNCkqMTAwYCUsIHdpdGggYSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgYHIgcm91bmQoc2Qoc3Vial9kZiR5KSw0KSoxMDBgJSwgaGlnaCBvZiBgciByb3VuZChtYXgoc3Vial9kZiR5KSw0KSoxMDBgJSwgYW5kIGxvdyBvZiBgciByb3VuZChtaW4oc3Vial9kZiR5KSw0KSoxMDBgJS4gVGhpcyBzaG93cyBkcmFtYXRpYyBkaWZmZXJlbmNlcyBpbiBpbmRpdmlkdWFsICpmb290cHJpbnRpbmcgZGlzdGluY3RpdmVuZXNzKi4KCgojIyMgNy4gUGVybXV0YXRpb24gVGVzdAoKVG8gYXNzZXNzIHRoZSBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2Ugb2YgdGhlIHJlc3VsdHMgYWJvdmUsIHdlIGNvbmR1Y3RlZCBhIG5vbi1wYXJhbWV0cmljIHBlcm11dGF0aW9uIHRlc3QuIFRvIGRvIHRoaXMsIHdlIHJhbmRvbWx5IHNjcmFtYmxlZCBwYWlyLXdpc2Ugc3ViamVjdC10by1kYXkgbGlua2FnZS4gV2UgcmVwZWF0ZWQgdGhpcyBwcm9jZXNzIGByIHBlcm1fdGltZWAgdGltZXMsIGFuZCBmb2xsb3dlZCB0aGUgc2FtZSBwcm9jZWR1cmUgYXMgYWJvdmUgdG8gb2J0YWluIGEgbnVsbCBkaXN0cmlidXRpb24gb2YgYXZlcmFnZSBtYXRjaCBhY2N1cmFjeSBhY3Jvc3Mgc2FtcGxlLCBhbmQgZm9yIGVhY2ggaW5kaXZpZHVhbC4KCmBgYHtyIGNhbGMgcGVybXV0YXRpb24gYWNjdXJhY3ksIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZ3BzX2RmX3Blcm0gPSBncHNfZGZfY2xlYW4yCnBlcm1fdGltZSA9IDEwMDAKcGVybV9hY2NfdGltZSA9IGxpc3QoKQpwZXJtX2FjY19zdWJqID0gbGlzdCgpCmZvciAoaSBpbiAxOnBlcm1fdGltZSkgewogIHBlcm1fcGFydF90aW1lcyA9IDEKICBwcmludChwYXN0ZSgicHJvY2Vzc2luZyAuLi4iLCBpLCIuLi4iKSkKICBncHNfZGZfcGVybSRJSUQgPSBzYW1wbGUoZ3BzX2RmX3Blcm0kSUlEKQogIHBlcm1fc3Vial9zZXEgPSBtYWtlX3N1Ympfc2VxKGdwc19kZl9wZXJtLCBwYXJ0X3RpbWVzID0gcGVybV9wYXJ0X3RpbWVzKQogIHBlcm1fZ3BzID0gbWFrZV9mZWF0dXJlX21hdHJpeChncHNfZGZfcGVybSxwZXJtX3N1Ympfc2VxLDM6MTcpCiAgcGVybV9tYXRfMSA9IHBlcm1fZ3BzJHN1YmpfbWF0XzEKICBwZXJtX21hdF8yID0gcGVybV9ncHMkc3Vial9tYXRfMgogIHBlcm1fbWF0Y2hfY29yID0gY2FsY19tYXRjaF9jb3IocGVybV9tYXRfMSxwZXJtX21hdF8yKQogIHBlcm1fYWNjX3RpbWVbW2ldXSA9IGNhbGNfYWNjX3RpbWUocGVybV9tYXRjaF9jb3IpCiAgcGVybV9hY2Nfc3VialtbaV1dID0gY2FsY19hY2Nfc3ViaihwZXJtX21hdGNoX2NvcikKfQpgYGAKCgpPdmVyIHRoZSBgciBwZXJtX3RpbWVgIHBlcm11dGF0aW9ucywgdGhlIG1lYW4gbWF0Y2ggYWNjdXJhY3kgd2FzIGByIHJvdW5kKG1lYW4ocGVybV9hY2NfdGltZV9hbGwpLDQpKjEwMGAlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGByIHJvdW5kKHNkKHBlcm1fYWNjX3RpbWVfYWxsKSw0KSoxMDBgJSwgaGlnaCBvZiBgciBtYXgobWVhbihwZXJtX2FjY190aW1lX2FsbCksNCkqMTAwYCUsIGFuZCBsb3cgb2YgYHIgbWluKG1lYW4ocGVybV9hY2NfdGltZV9hbGwpLDQpKjEwMGAlLig5NSVDSTogYHIgcm91bmQoQ0kocGVybV9hY2NfdGltZV9hbGwpWydsb3dlciddLDIpYC1gciByb3VuZChDSShwZXJtX2FjY190aW1lX2FsbClbJ3VwcGVyJ10sMilgKQoKYGBge3IgY29tYmluZSBwYXJ0aXRpb24gcGVybXV0YXRpb24gcmVzdWx0cywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KcGVybV9hY2NfdGltZV9hbGwgPSB1bmxpc3QocGVybV9hY2NfdGltZSkKcV90aW1lX2NvciA9IGhpc3RfY2h4KHBlcm1fYWNjX3RpbWVfYWxsLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZSgiQXZlcmFnZSBBY2N1cmFjeSBBY3Jvc3MiLGxlbmd0aChwZXJtX2FjY190aW1lX2FsbCksIlBlcm11dGF0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQpnZ3Bsb3RseShxX3RpbWVfY29yKQpgYGAKKipGaWd1cmUgOTogTWF0Y2ggYWNjdXJhY3kgZGlzdHJpYnV0b24gYWNyb3NzIGByIHBlcm1fdGltZWAgcGVybXV0YXRpb25zLioqIE1lYW4gbWF0Y2ggYWNjdXJhY3kgaW4gcGVybXV0YXRpb24gd2FzIGByIHJvdW5kKENJKHBlcm1fYWNjX3RpbWVfYWxsKVsnbWVhbiddLDIpYCAoOTUlQ0k6IGByIHJvdW5kKENJKHBlcm1fYWNjX3RpbWVfYWxsKVsnbG93ZXInXSwyKWAtYHIgcm91bmQoQ0kocGVybV9hY2NfdGltZV9hbGwpWyd1cHBlciddLDIpYCkuIFRoaXMgbnVsbCBkaXN0cmlidXRpb24gZG9lcyBub3Qgb3ZlcmxhcHAgd2l0aCB0aGUgZGlzdHJpYnV0b24gdXNpbmcgcmVhbCBkYXRhICgqKkZpZy43KiopIGF0IGFsbC4KCgpXZSBhbHNvIGNhbGN1bGF0ZWQgbnVsbCBkaXN0cmlidXRpb24gb2YgbWF0Y2ggYWNjdXJhY3kgZm9yIGVhY2ggc3ViamVjdC4gT3ZlciB0aGUgYHIgcGVybV90aW1lYCBwZXJtdXRhdGlvbnMsIHRoZSBtZWFuIG1hdGNoIGFjY3VyYWN5IHdhcyBgciByb3VuZChtZWFuKHN1YmpfZGYkeV9wZXJtKSw0KSoxMDBgJSwgd2l0aCBhIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBgciByb3VuZChzZChzdWJqX2RmJHlfcGVybSksNCkqMTAwYCUsIGhpZ2ggb2YgYHIgcm91bmQobWF4KHN1YmpfZGYkeV9wZXJtKSw0KSoxMDBgJSwgYW5kIGxvdyBvZiBgciByb3VuZChtaW4oc3Vial9kZiR5X3Blcm0pLDQpKjEwMGAlLgoKYGBge3IgY29tYmluZSBzdWJqIHBlcm11dGF0aW9uIGFuYWx5c2lzfQpwZXJtX3N1YmogPSBsaXN0KCkKZm9yIChzdWJqIGluIHN1YmpfYWNjX3Bsb3QkZGF0YSR4KSB7CiAgcGVybV9zdWJqJHZhbFtbc3Vial1dID0gc2FwcGx5KHBlcm1fYWNjX3N1YmosIGZ1bmN0aW9uKHBlcm0pIHBlcm1bd2hpY2gobmFtZXMocGVybSkgPT0gc3ViaildKQogIHBlcm1fc3ViaiRoaXN0W1tzdWJqXV0gPSBoaXN0X2NoeChwZXJtX3N1YmpbW3N1YmpdXSR2YWwsIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKHN1YmosIjogQWNjdXJhY3kgQWNyb3NzIFxuIixwZXJtX3RpbWUsIlBlcm11dGF0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQogIHBlcm1fc3ViaiRhY2NbW3N1YmpdXSA9IHN1bShwZXJtX3N1YmokdmFsW1tzdWJqXV0pL3Blcm1fdGltZQp9CmBgYAoKYGBge3IgcGxvdCBzdWJqIGFuYWx5c2lzIHdpdGggcGVybSwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0Kc3Vial9kZiA8LSBkYXRhLmZyYW1lKHg9bmFtZXMoc3Vial9zZXEpKQpzdWJqX2RmJHkgPSBhY2Nfc3ViagpzdWJqX2RmJHlfcGVybSA9IHVubGlzdChwZXJtX3N1YmokYWNjKQpzdWJqX2RmID0gc3Vial9kZltvcmRlcihzdWJqX2RmJHkpLF0KcF9zdWJqX2Nvcl9wZXJtID0gZ2dwbG90KHN1YmpfZGYsIGFlcyh4ID0gcmVvcmRlcih4LCB5KSwgeSA9IHZhbHVlKSkgKyAKICBnZW9tX3BvaW50KGFlcyh5ID0geSwgY29sID0gInN1YmplY3QgZGF0YSIpKSArIAogIGdlb21fcG9pbnQoYWVzKHkgPSB5X3Blcm0sIGNvbCA9ICJwZXJtdXRhdGlvbiIpKSArCiAgdGhlbWVfY293cGxvdCgpICsgCiAgbGFicyh0aXRsZSA9IHBhc3RlKCJDb3IgRmVhdHVyZXMgXG4gU3ViamVjdCBMZXZlbCBBY2N1cmFjeSBBY3Jvc3MiLHBhcnRfdGltZXMsIkRhdGEgUGFydGl0aW9ucyIpLCAKICAgICAgIHggPSAiU3ViamVjdHMiLCB5ID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCBzaXplID0gOCksIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpnZ3Bsb3RseShwX3N1YmpfY29yX3Blcm0pCmBgYAoqKkZpZ3VyZSA4OiBNYXRjaCBhY2N1cmFjeSBudWxsIGRpc3RyaWJ1dG9uIGFjcm9zcyBgciBsZW5ndGgodW5pcXVlKGdwc19kZl9jbGVhbjIkSUlEKSlgIHN1YmplY3RzLioqIE1lYW4gbWF0Y2ggYWNjdXJhY3kgaW4gcGVybXV0YXRpb24gd2FzIGByIHJvdW5kKG1lYW4oc3Vial9kZiR5X3Blcm0pLDQpKjEwMGAlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGByIHJvdW5kKHNkKHN1YmpfZGYkeV9wZXJtKSw0KSoxMDBgJSwgaGlnaCBvZiBgciByb3VuZChtYXgoc3Vial9kZiR5X3Blcm0pLDQpKjEwMGAlLCBhbmQgbG93IG9mIGByIHJvdW5kKG1pbihzdWJqX2RmJHlfcGVybSksNCkqMTAwYCUuIFdoaWxlIGluZGl2aWR1YWxzIGV4aGliaXRlZCBtYXJrZWQgZGlmZmVyZW5jZXMgaW4gZm9vdHByaW50aW5nIGRpc3RpbmN0aXZlbnNzLCB0aGUgc3ViamVjdCB3aXRoIHRoZSBsb3dlc3QgcHJlZGljdGlvbiBhY2N1cmFjeSB3YXMgc3RpbGwgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBhZ2FpbnN0IHRoZSBwZXJtdXRhdGlvbiB0ZXN0IChpLmUuIGZvciBzdWJqZWN0IGByIHN1YmpfZGYkeFsxXWA6IGRhdGE6IGByIHJvdW5kKG1pbihzdWJqX2RmJHkpLDQpKjEwMGAgJSB2cy4gcGVybXV0YXRpb246IGByIHJvdW5kKG1pbihzdWJqX2RmJHlfcGVybSksNCkqMTAwYCUpLgoKCiMjIyA4LiBNZWFuIEZlYXR1cmVzCgpXZSBjYWxjdWxhdGVkIHRoZSBgbWVhbmAgb2YgZWFjaCBHUFMgZmVhdHVyZSBhcyBhIG1ldHJpYyBvZiBmZWF0dXJlIHZhcmlhYmlsaXR5LiBTaW1pbGFyIHRvIGFib3ZlLCB3ZSBjYWxjdWxhdGVkIGBtZWFuYCBzZXBhcmF0ZWx5IGZvciBlYWNoIGRhdGEgcGFydGl0aW9uIGFuZCBmb3IgZWFjaCBpbmRpdmlkdWFsLgoKYGBge3IgY2FsYyBtZWFuIEdQUyBmZWF0dXJlc30KbWVhbl9ncHMgPSBmdW5jdGlvbihncHNfZGYpewogIG1lYW5fbGlzdCA9IHNhcHBseSgzOjE3LGZ1bmN0aW9uKGkpIG1lYW4oZ3BzX2RmWyxpXSxuYS5ybT1UKSkKICBuYW1lcyhtZWFuX2xpc3QpID0gY29sbmFtZXMoZ3BzX2RmKVszOjE3XQogIHJldHVybihtZWFuX2xpc3QpCn0KCm1ha2VfZmVhdHVyZV9tZWFuID0gZnVuY3Rpb24oZ3BzX2RmLCBzdWJqX3NlcSkgewogIHN1YmpfbWF0XzEgPSBsaXN0KCkKICBzdWJqX21hdF8yID0gbGlzdCgpCiAgZm9yIChzdWJqIGluIHVuaXF1ZShncHNfZGYkSUlEKSl7CiAgICBzdWJqX2RhdGEgPSBzdWJzZXQoZ3BzX2RmX2NsZWFuMiwgSUlEPT1zdWJqKQogICAgc3Vial9tYXRfMVtbc3Vial1dID0gbGFwcGx5KHN1Ympfc2VxW1tzdWJqXV0sIGZ1bmN0aW9uKGxpc3QpIG1lYW5fZ3BzKHN1YmpfZGF0YVtsaXN0LF0pKQogICAgc3Vial9tYXRfMltbc3Vial1dID0gbGFwcGx5KHN1Ympfc2VxW1tzdWJqXV0sIGZ1bmN0aW9uKGxpc3QpIG1lYW5fZ3BzKHN1YmpfZGF0YVstbGlzdCxdKSkKICB9CiAgcmV0dXJuKGxpc3Qoc3Vial9tYXRfMSA9IHN1YmpfbWF0XzEsIHN1YmpfbWF0XzIgPSBzdWJqX21hdF8yICkpCn0KCmdwc19jbGVhbjJfbWVhbl9mZWF0ID0gbWFrZV9mZWF0dXJlX21lYW4oZ3BzX2RmX2NsZWFuMiwgc3Vial9zZXEpCmBgYAoKYGBge3Igd2FycCBtZWFuIGZlYXR1cmUgbGlzdCB0byBkYXRhIGZyYW1lLCBmaWcuaGVpZ2h0PTYsIGVjaG89Rn0KbWVhbl9kZiA9IGxpc3QoKQpmb3IgKHN1YmogaW4gdW5pcXVlKGdwc19kZiRJSUQpKSB7CiAgbWVhbl9kZltbc3Vial1dID0gZGF0YS5mcmFtZShtYXRyaXgoTkEsIHBhcnRfdGltZXMsIGxlbmd0aChjb2xuYW1lcyhncHNfZGZfY2xlYW4pKS0yKSkKICBmb3IgKGkgaW4gMTpwYXJ0X3RpbWVzKXsKICAgIG1lYW5fZGZbW3N1YmpdXVtpLF0gPSBncHNfY2xlYW4yX21lYW5fZmVhdCRzdWJqX21hdF8xW1tzdWJqXV1bW2ldXQogIH0KICBjb2xuYW1lcyhtZWFuX2RmW1tzdWJqXV0pID0gY29sbmFtZXMoZ3BzX2RmX2NsZWFuWzM6MTddKQp9CgptZWFuX3Bsb3RzID0gbGlzdCgpCmZvciAoZ3BzX2Z0IGluIGNvbG5hbWVzKGdwc19kZl9jbGVhblszOjE3XSkpewogIG1lYW5fcGxvdHNbW2dwc19mdF1dID0gaGlzdF9jaHgoc2FwcGx5KG1lYW5fZGYsIGZ1bmN0aW9uKGRmKSBkZlsxLGdwc19mdF0pLCB0aXRsZSA9IGdwc19mdCwgeGF4aXMgPSAiTWVhbiIsIHlheGlzID0gImNvdW50IikKfQoKUmVkdWNlKGArYCwgbWVhbl9wbG90cykKYGBgCioqRmlndXJlIDk6IEZlYXR1cmUgbWVhbiBhY3Jvc3MgYWxsIHN1YmplY3RzKiouIFRoZSBoaXN0b2dyYW0gYXJlIGZvciBkYXRhIGluIHRoZSBmaXJzdCByYW5kb20gaGFsZiBzcGxpdC4KCgoKIyMjIDkuIFZhcmlhYmlsaXR5IEZlYXR1cmVzCldlIGNhbGN1bGF0ZWQgdGhlIGByb290IG1lYW4gc3F1YXJlIG9mIHN1Y2Nlc3NpdmUgZGlmZmVyZW5jZXNgIG9yIGBSTVNTRGAgb2YgZWFjaCBHUFMgZmVhdHVyZSBhcyBhIG1ldHJpYyBvZiBmZWF0dXJlIHZhcmlhYmlsaXR5LiBTaW1pbGFyIHRvIGFib3ZlLCB3ZSBjYWxjdWxhdGVkIGBSTVNTRGAgc2VwYXJhdGVseSBmb3IgZWFjaCBkYXRhIHBhcnRpdGlvbiBhbmQgZm9yIGVhY2ggaW5kaXZpZHVhbC4KCmBgYHtyIGNhbGN1bGF0ZSB2YXJpYWJpbGl0eSBmZWF0dXJlc30Kcm1zc2RfZ3BzID0gZnVuY3Rpb24oZ3BzX2RmKXsKICBybXNzZF9saXN0ID0gc2FwcGx5KDM6MTcsZnVuY3Rpb24oaSkgcm1zc2RfaWQoZ3BzX2RmWyxpXSwgZ3BzX2RmJElJRCxsb25nPUYpKQogIG5hbWVzKHJtc3NkX2xpc3QpID0gY29sbmFtZXMoZ3BzX2RmKVszOjE3XQogIHJldHVybihybXNzZF9saXN0KQp9CgptYWtlX2ZlYXR1cmVfcm1zc2QgPSBmdW5jdGlvbihncHNfZGYsIHN1Ympfc2VxKSB7CiAgc3Vial9tYXRfMSA9IGxpc3QoKQogIHN1YmpfbWF0XzIgPSBsaXN0KCkKICBmb3IgKHN1YmogaW4gdW5pcXVlKGdwc19kZiRJSUQpKXsKICAgIHN1YmpfZGF0YSA9IHN1YnNldChncHNfZGZfY2xlYW4yLCBJSUQ9PXN1YmopCiAgICBzdWJqX21hdF8xW1tzdWJqXV0gPSBsYXBwbHkoc3Vial9zZXFbW3N1YmpdXSwgZnVuY3Rpb24obGlzdCkgcm1zc2RfZ3BzKHN1YmpfZGF0YVtsaXN0LF0pKQogICAgc3Vial9tYXRfMltbc3Vial1dID0gbGFwcGx5KHN1Ympfc2VxW1tzdWJqXV0sIGZ1bmN0aW9uKGxpc3QpIHJtc3NkX2dwcyhzdWJqX2RhdGFbLWxpc3QsXSkpCiAgfQogIHJldHVybihsaXN0KHN1YmpfbWF0XzEgPSBzdWJqX21hdF8xLCBzdWJqX21hdF8yID0gc3Vial9tYXRfMiApKQp9CgpncHNfY2xlYW4yX3Jtc3NkX2ZlYXQgPSBtYWtlX2ZlYXR1cmVfcm1zc2QoZ3BzX2RmX2NsZWFuMiwgc3Vial9zZXEpCmBgYAoKYGBge3Igd2FycCB2YXIgZmVhdHVyZSBsaXN0IHRvIGRhdGEgZnJhbWUsIGZpZy5oZWlnaHQ9NiwgZWNobz1GfQp2YXJfZGYgPSBsaXN0KCkKZm9yIChzdWJqIGluIHVuaXF1ZShncHNfZGYkSUlEKSkgewogIHZhcl9kZltbc3Vial1dID0gZGF0YS5mcmFtZShtYXRyaXgoTkEsIHBhcnRfdGltZXMsIGxlbmd0aChjb2xuYW1lcyhncHNfZGZfY2xlYW4pKS0yKSkKICBmb3IgKGkgaW4gMTpwYXJ0X3RpbWVzKXsKICAgIHZhcl9kZltbc3Vial1dW2ksXSA9IGdwc19jbGVhbjJfcm1zc2RfZmVhdCRzdWJqX21hdF8xW1tzdWJqXV1bW2ldXQogIH0KICBjb2xuYW1lcyh2YXJfZGZbW3N1YmpdXSkgPSBjb2xuYW1lcyhncHNfZGZfY2xlYW5bMzoxN10pCn0KCnZhcl9wbG90cyA9IGxpc3QoKQpmb3IgKGdwc19mdCBpbiBjb2xuYW1lcyhncHNfZGZfY2xlYW5bMzoxN10pKXsKICB2YXJfcGxvdHNbW2dwc19mdF1dID0gaGlzdF9jaHgoc2FwcGx5KHZhcl9kZiwgZnVuY3Rpb24oZGYpIGRmWzEsZ3BzX2Z0XSksIHRpdGxlID0gZ3BzX2Z0LCB4YXhpcyA9ICJWYXJpYWJpbGl0eShSTVNTRCkiLCB5YXhpcyA9ICJjb3VudCIpCn0KClJlZHVjZShgK2AsIHZhcl9wbG90cykKYGBgCioqRmlndXJlIDEwOiBGZWF0dXJlIHZhcmlhYmlsaXR5IGZvciBhbGwgc3ViamVjdHMqKi4gVmFyaWFiaWxpdHkgaXMgbWVhc3VyZWQgYnkgYHJvb3QgbWVhbiBzcXVhcmUgb2Ygc3VjY2Vzc2l2ZSBkaWZmZXJlbmNlc2AgKFJNU1NEKS4gVGhlIGhpc3RvZ3JhbXMgYXJlIGZvciBkYXRhIGluIHRoZSBmaXJzdCByYW5kb20gaGFsZiBzcGxpdC4KCgoKIyMjIDEwLiBQcmVkaWN0IHdpdGggTmV3IEZlYXR1cmVzIApgYGB7ciBkZWZpbmUgbWF0Y2ggZnVuY3Rpb24gZm9yIHZlY3RvciBmZWF0dXJlc30KY2FsY19tYXRjaF92ZWN0b3IgPSBmdW5jdGlvbihzdWJqX21hdF8xLHN1YmpfbWF0XzIsbWV0aG9kKSB7CiAgcGFydF90aW1lcyA9IGxlbmd0aChzdWJqX21hdF8xW1sxXV0pCiAgZGF0YWJhc2UgPSBsaXN0KCkgCiAgZm9yICh0aW1lIGluIDE6cGFydF90aW1lcykgewogICAgICBkYXRhYmFzZVtbdGltZV1dID0gbGFwcGx5KHN1YmpfbWF0XzIsIGZ1bmN0aW9uKHN1YmptYXQpIHN1YmptYXRbW3RpbWVdXSkKICB9CiAgCiAgICAjIG1hdGNoIHRhcmdldCB0byBkYXRhYmFzZQogICAgbWF0Y2hfY29yID0gbGlzdCgpCiAgICBmb3IgKHN1YmoxIGluIG5hbWVzKHN1YmpfbWF0XzEpKXsgI2xvb3AgdGhyb3VnaCBlYWNoIHN1YmoKICAgICAgIyBjcmVhdGUgYSBsaXN0IG9mIHRhcmdldCBhY3Jvc3MgcGFydGl0aW9ucwogICAgICB0YXJnZXRfbGlzdCA9IGxhcHBseShzdWJqX21hdF8xW1tzdWJqMV1dLCBmdW5jdGlvbihwYXJ0KSBwYXJ0KQogICAgICAjIGNyZWF0ZSBhIG1hdGNoIGxpc3QKICAgICAgZm9yICh0aW1lIGluIDE6cGFydF90aW1lcyl7CiAgICAgICAgdGFyZ2V0X3N1YmpfdGltZSA9IHRhcmdldF9saXN0W1t0aW1lXV0gI2xvb3AgdGhyb3VnaCBlYWNoIHBhcnRpdGlvbgogICAgICAgIGZvciAoc3ViajIgaW4gbmFtZXMoc3Vial9tYXRfMikpeyAjbG9vcCBldmVyeW9uZSBpbiAybmQgaGFsZgogICAgICAgICAgZGF0YV9zdWJqX3RpbWUgPSBzdWJqX21hdF8yW1tzdWJqMl1dW1t0aW1lXV0KICAgICAgICAgIGlmIChtZXRob2QgPT0gImNvciIpIHsKICAgICAgICAgIG1hdGNoX2Nvcltbc3ViajFdXVtbYXMuY2hhcmFjdGVyKHRpbWUpXV1bW3N1YmoyXV0gPSBjb3IodGFyZ2V0X3N1YmpfdGltZSxkYXRhX3N1YmpfdGltZSx1c2UgPSAibmEub3IuY29tcGxldGUiKQogICAgICAgICAgfSAKICAgICAgICAgIGVsc2UgaWYgKG1ldGhvZCA9PSAicm1zZSIpIHsKICAgICAgICAgICAgbWF0Y2hfY29yW1tzdWJqMV1dW1thcy5jaGFyYWN0ZXIodGltZSldXVtbc3ViajJdXSA9IHJtc2UodGFyZ2V0X3N1YmpfdGltZSxkYXRhX3N1YmpfdGltZSkKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0KICByZXR1cm4obWF0Y2hfY29yKQp9CmBgYAoKCmBgYHtyIGNhbGMgbWF0Y2ggYWNjdXJhY3kgdXNpbmcgbWVhbiBmZWF0dXJlc30KbWF0Y2hfbWVhbl9ybXNlID0gY2FsY19tYXRjaF92ZWN0b3IoZ3BzX2NsZWFuMl9tZWFuX2ZlYXQkc3Vial9tYXRfMSxncHNfY2xlYW4yX21lYW5fZmVhdCRzdWJqX21hdF8yLCJybXNlIikKYWNjX3RpbWVfbWVhbl9ybXNlID0gY2FsY19hY2NfdGltZShtYXRjaF9tZWFuX3Jtc2UsIm1pbiIpCmFjY19zdWJqX21lYW5fcm1zZSA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfbWVhbl9ybXNlLCJtaW4iKQoKbWF0Y2hfbWVhbl9jb3IgPSBjYWxjX21hdGNoX3ZlY3RvcihncHNfY2xlYW4yX21lYW5fZmVhdCRzdWJqX21hdF8xLGdwc19jbGVhbjJfbWVhbl9mZWF0JHN1YmpfbWF0XzIsImNvciIpCmFjY190aW1lX21lYW5fY29yID0gY2FsY19hY2NfdGltZShtYXRjaF9tZWFuX2NvciwibWF4IikKYWNjX3N1YmpfbWVhbl9jb3IgPSBjYWxjX2FjY19zdWJqKG1hdGNoX21lYW5fY29yLCJtYXgiKQpgYGAKCmBgYHtyIGNhbGMgbWF0Y2ggYWNjdXJhY3kgdXNpbmcgdmFyaWFiaWxpdHkgZmVhdHVyZXN9Cm1hdGNoX3Jtc3NkX3Jtc2UgPSBjYWxjX21hdGNoX3ZlY3RvcihncHNfY2xlYW4yX3Jtc3NkX2ZlYXQkc3Vial9tYXRfMSxncHNfY2xlYW4yX3Jtc3NkX2ZlYXQkc3Vial9tYXRfMiwgInJtc2UiKQphY2NfdGltZV9ybXNzZF9ybXNlID0gY2FsY19hY2NfdGltZShtYXRjaF9ybXNzZF9ybXNlLCAibWluIikKYWNjX3N1Ympfcm1zc2Rfcm1zZSA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfcm1zc2Rfcm1zZSwgIm1pbiIpCgoKbWF0Y2hfcm1zc2RfY29yID0gY2FsY19tYXRjaF92ZWN0b3IoZ3BzX2NsZWFuMl9ybXNzZF9mZWF0JHN1YmpfbWF0XzEsZ3BzX2NsZWFuMl9ybXNzZF9mZWF0JHN1YmpfbWF0XzIsICJjb3IiKQphY2NfdGltZV9ybXNzZF9jb3IgPSBjYWxjX2FjY190aW1lKG1hdGNoX3Jtc3NkX2NvciwgIm1heCIpCmFjY19zdWJqX3Jtc3NkX2NvciA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfcm1zc2RfY29yLCAibWF4IikKYGBgCgojIyMgMTEuIFBlcm11dGF0aW9uIFRlc3RzIHdpdGggTmV3IEZlYXR1cmVzIAoKIyMjIyAxMWEuIEF2ZXJhZ2UgQWNjdXJhY3kKYGBge3IgZGVmaW5lIG5ldyBwZXJtdXRhdGlvbiBmdW5jdGlvbnN9CnBlcm1fdmVjdG9yID0gZnVuY3Rpb24oZ3BzX2RmLCBwZXJtX3RpbWUsIG1ldGhvZF9mZWF0dXJlLCBtZXRob2RfbWF0Y2gpewogIGdwc19kZl9wZXJtID0gZ3BzX2RmCiAgcGVybV9hY2NfdGltZSA9IGxpc3QoKQogIHBlcm1fYWNjX3N1YmogPSBsaXN0KCkKICBmb3IgKGkgaW4gMTpwZXJtX3RpbWUpIHsKICAgIHBlcm1fcGFydF90aW1lcyA9IDEKICAgIHByaW50KHBhc3RlKCJwcm9jZXNzaW5nIC4uLiIsIGksIi4uLiIpKQogICAgZ3BzX2RmX3Blcm0kSUlEID0gc2FtcGxlKGdwc19kZl9wZXJtJElJRCkKICAgIHBlcm1fc3Vial9zZXEgPSBtYWtlX3N1Ympfc2VxKGdwc19kZl9wZXJtLHBhcnRfdGltZXMgPSBwZXJtX3BhcnRfdGltZXMpCiAgICBpZiAobWV0aG9kX2ZlYXR1cmUgPT0gInJtc3NkIikgewogICAgICBwZXJtX2dwcyA9IG1ha2VfZmVhdHVyZV9ybXNzZChncHNfZGZfcGVybSxwZXJtX3N1Ympfc2VxKQogICAgfSBlbHNlIGlmIChtZXRob2RfZmVhdHVyZSA9PSAibWVhbiIpewogICAgICBwZXJtX2dwcyA9IG1ha2VfZmVhdHVyZV9tZWFuKGdwc19kZl9wZXJtLHBlcm1fc3Vial9zZXEpCiAgICB9IGVsc2UgaWYgKG1ldGhvZF9mZWF0dXJlID09ICJjb21iaW5lZCIpewogICAgICBwZXJtX2dwc19ybXNzZCA9IG1ha2VfZmVhdHVyZV9ybXNzZChncHNfZGZfcGVybSxwZXJtX3N1Ympfc2VxKQogICAgICBwZXJtX2dwc19tZWFuID0gbWFrZV9mZWF0dXJlX21lYW4oZ3BzX2RmX3Blcm0scGVybV9zdWJqX3NlcSkKICAgICAgcGVybV9ncHNfY29yID0gbWFrZV9mZWF0dXJlX21hdHJpeChncHNfZGZfcGVybSxwZXJtX3N1Ympfc2VxLDM6MTcpCiAgICAgIHN1YmpfbWF0XzEgPSBsaXN0KCkKICAgICAgc3Vial9tYXRfMiA9IGxpc3QoKQogICAgICBmb3IgKHN1YmogaW4gbmFtZXMocGVybV9ncHNfY29yJHN1YmpfbWF0XzEpKXsKICAgICAgICAgICNwcmludChzdWJqKQogICAgICAgICAgc3Vial9tYXRfMVtbc3Vial1dJFJlc2FtcGxlMSA9IGFzLm51bWVyaWMoIGModmFsdWUocGVybV9ncHNfcm1zc2Qkc3Vial9tYXRfMVtbc3Vial1dW1sxXV0pLHZhbHVlKHBlcm1fZ3BzX21lYW4kc3Vial9tYXRfMVtbc3Vial1dW1sxXV0pLHBlcm1fZ3BzX2NvciRzdWJqX21hdF8xW1tzdWJqXV1bWzFdXSRjb3IpKQogICAgICAgICAgc3Vial9tYXRfMltbc3Vial1dJFJlc2FtcGxlMSA9IGFzLm51bWVyaWMoIGModmFsdWUocGVybV9ncHNfcm1zc2Qkc3Vial9tYXRfMltbc3Vial1dW1sxXV0pLHZhbHVlKHBlcm1fZ3BzX21lYW4kc3Vial9tYXRfMltbc3Vial1dW1sxXV0pLHBlcm1fZ3BzX2NvciRzdWJqX21hdF8yW1tzdWJqXV1bWzFdXSRjb3IpKQogICAgICB9CiAgICAgIHBlcm1fZ3BzID0gbGlzdChzdWJqX21hdF8xID0gc3Vial9tYXRfMSwgc3Vial9tYXRfMiA9IHN1YmpfbWF0XzIpCiAgICB9CiAgICBwZXJtX21hdF8xID0gcGVybV9ncHMkc3Vial9tYXRfMQogICAgcGVybV9tYXRfMiA9IHBlcm1fZ3BzJHN1YmpfbWF0XzIKICAgIGlmIChtZXRob2RfbWF0Y2ggPT0gImNvciIpIHsKICAgICAgcGVybV9tYXRjaCA9IGNhbGNfbWF0Y2hfdmVjdG9yKHBlcm1fbWF0XzEscGVybV9tYXRfMiwgImNvciIpCiAgICAgIHBlcm1fYWNjX3RpbWVbW2ldXSA9IGNhbGNfYWNjX3RpbWUocGVybV9tYXRjaCwgIm1heCIpCiAgICAgIHBlcm1fYWNjX3N1YmpbW2ldXSA9IGNhbGNfYWNjX3N1YmoocGVybV9tYXRjaCwgIm1heCIpCiAgICB9CiAgICBlbHNlIGlmIChtZXRob2RfbWF0Y2ggPT0gInJtc2UiKXsKICAgICAgcGVybV9tYXRjaCA9IGNhbGNfbWF0Y2hfdmVjdG9yKHBlcm1fbWF0XzEscGVybV9tYXRfMiwgInJtc2UiKQogICAgICBwZXJtX2FjY190aW1lW1tpXV0gPSBjYWxjX2FjY190aW1lKHBlcm1fbWF0Y2gsIm1pbiIpCiAgICAgIHBlcm1fYWNjX3N1YmpbW2ldXSA9IGNhbGNfYWNjX3N1YmoocGVybV9tYXRjaCwibWluIikKICAgIH0KICB9CiAgcmV0dXJuKGxpc3QocGVybV9hY2NfdGltZT0gcGVybV9hY2NfdGltZSxwZXJtX2FjY19zdWJqID0gcGVybV9hY2Nfc3ViaikpCn0KYGBgCgoKYGBge3IgcnVuIHBlcm11dGF0aW9uIGZvciBtZWFuIGZlYXR1cmVzIG1hdGNoIGJ5IGNvciwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnBlcm1fbWVhbl9jb3IgPSBwZXJtX3ZlY3RvcihncHNfZGZfY2xlYW4yLCAxMDAwLCAibWVhbiIsICJjb3IiKQoKcF90aW1lX21lYW5fY29yID0gaGlzdF9jaHgoYWNjX3RpbWVfbWVhbl9jb3IsIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJNZWFuIEZlYXR1cmVzOiBcbiBBdmVyYWdlIEFjY3VyYWN5IEFjcm9zcyIscGFydF90aW1lcywiRGF0YSBQYXJ0aXRpb25zIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCgpxX3RpbWVfbWVhbl9jb3IgPSAgaGlzdF9jaHgodW5saXN0KHBlcm1fbWVhbl9jb3IkcGVybV9hY2NfdGltZSksIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJNZWFuIEZlYXR1cmVzOiBcbiBBdmVyYWdlIEFjY3VyYWN5IEFjcm9zcyIsbGVuZ3RoKHBlcm1fbWVhbl9jb3IkcGVybV9hY2NfdGltZSksIlBlcm11dGF0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0gNiwgbWVzc2FnZT1GQUxTRX0KcF90aW1lX21lYW5fY29yICsgcV90aW1lX21lYW5fY29yCmBgYAoKKipGaWd1cmUgMTE6IFByZWRpY3Rpb24gYWNjdXJhY3kgdXNpbmcgbWVhbiBmZWF0dXJlcyBhbmQgbWF0Y2hlZCBieSBtYXggcGVhcnNvbiBjb3JyZWxhdGlvbioqLgoKYGBge3IgcnVuIHBlcm11dGF0aW9uIGZvciBtZWFuIGZlYXR1cmVzIG1hdGNoIGJ5IHJtc2UsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpwZXJtX21lYW5fcm1zZSA9IHBlcm1fdmVjdG9yKGdwc19kZl9jbGVhbjIsIDEwMDAsICJtZWFuIiwgInJtc2UiKQoKcF90aW1lX21lYW5fcm1zZSA9IGhpc3RfY2h4KGFjY190aW1lX21lYW5fcm1zZSwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoIk1lYW4gRmVhdHVyZXM6IFxuIEF2ZXJhZ2UgQWNjdXJhY3kgQWNyb3NzIixwYXJ0X3RpbWVzLCJEYXRhIFBhcnRpdGlvbnMiKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKCnFfdGltZV9tZWFuX3Jtc2UgPSAgaGlzdF9jaHgodW5saXN0KHBlcm1fbWVhbl9ybXNlJHBlcm1fYWNjX3RpbWUpLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZSgiTWVhbiBGZWF0dXJlczogXG4gQXZlcmFnZSBBY2N1cmFjeSBBY3Jvc3MiLGxlbmd0aChwZXJtX21lYW5fcm1zZSRwZXJtX2FjY190aW1lKSwiUGVybXV0YXRpb25zIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCgpgYGAKCmBgYHtyIGZpZy53aWR0aD0gNiwgbWVzc2FnZT1GQUxTRX0KcF90aW1lX21lYW5fcm1zZSArIHFfdGltZV9tZWFuX3Jtc2UKYGBgCgoqKkZpZ3VyZSAxMTogUHJlZGljdGlvbiBhY2N1cmFjeSB1c2luZyBtZWFuIGZlYXR1cmVzIGFuZCBtYXRjaGVkIGJ5IG1pbiByb290IG1lYW4gc3F1YXJlZCoqLgoKCmBgYHtyIHJ1biBwZXJtdXRhdGlvbiBmb3Igcm1zc2QgZmVhdHVyZXMgbWF0Y2hlZCBieSBjb3IsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpwZXJtX3Jtc3NkX2NvciA9IHBlcm1fdmVjdG9yKGdwc19kZl9jbGVhbjIsIDEwMDAsICJybXNzZCIsImNvciIpCgpwX3RpbWVfcm1zc2RfY29yID0gaGlzdF9jaHgoYWNjX3RpbWVfcm1zc2RfY29yLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZSgiUk1TU0QgRmVhdHVyZXM6IFxuIEF2ZXJhZ2UgQWNjdXJhY3kgQWNyb3NzIixwYXJ0X3RpbWVzLCJEYXRhIFBhcnRpdGlvbnMiKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKCnFfdGltZV9ybXNzZF9jb3IgPSBoaXN0X2NoeCh1bmxpc3QocGVybV9ybXNzZF9jb3IkcGVybV9hY2NfdGltZSksIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJSTVNTRCBGZWF0dXJlczogQXZlcmFnZSBBY2N1cmFjeSBBY3Jvc3MiLGxlbmd0aChwZXJtX3Jtc3NkX2NvciRwZXJtX2FjY190aW1lX2NvciksIlBlcm11dGF0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQpgYGAKCmBgYHtyIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFfQpwX3RpbWVfcm1zc2RfY29yICsgcV90aW1lX3Jtc3NkX2NvcgpgYGAKCioqRmlndXJlIDEyOiBQcmVkaWN0aW9uIGFjY3VyYWN5IHVzaW5nIFJNU1NEIGZlYXR1cmVzIGFuZCBtYXRjaGVkIGJ5IG1heCBwZWFyc29uIGNvcnJlbGF0aW9uKiouCgpgYGB7ciBydW4gcGVybXV0YXRpb24gZm9yIHJtc3NkIGZlYXR1cmVzIG1hdGNoZWQgYnkgcm1zZSwgbWVzc2FnZT1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KcGVybV9ybXNzZF9ybXNlID0gcGVybV92ZWN0b3IoZ3BzX2RmX2NsZWFuMiwgMTAwMCwgInJtc3NkIiwicm1zZSIpCgpwX3RpbWVfcm1zc2Rfcm1zZSA9IGhpc3RfY2h4KGFjY190aW1lX3Jtc3NkX3Jtc2UsIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJSTVNTRCBGZWF0dXJlczogXG4gQXZlcmFnZSBBY2N1cmFjeSBBY3Jvc3MiLGxlbmd0aChhY2NfdGltZV9ybXNzZF9jb3IpLCJEYXRhIFBhcnRpdGlvbnMiKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKCnFfdGltZV9ybXNzZF9ybXNlID0gaGlzdF9jaHgodW5saXN0KHBlcm1fcm1zc2Rfcm1zZSRwZXJtX2FjY190aW1lKSwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoIlJNU1NEIEZlYXR1cmVzOiBBdmVyYWdlIEFjY3VyYWN5IEFjcm9zcyIsbGVuZ3RoKHBlcm1fcm1zc2Rfcm1zZSRwZXJtX2FjY190aW1lX2NvciksIlBlcm11dGF0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQpgYGAKCmBgYHtyIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFfQpwX3RpbWVfcm1zc2Rfcm1zZSArIHFfdGltZV9ybXNzZF9ybXNlCmBgYAoKKipGaWd1cmUgMTM6IFByZWRpY3Rpb24gYWNjdXJhY3kgdXNpbmcgUk1TU0QgZmVhdHVyZXMgYW5kIG1hdGNoZWQgYnkgbWluIHJvb3QgbWVhbiBzcXVhcmVkKiouCgpgYGB7ciBkZWZpbmUgY29tYmluZSBwZXJtIHN1YmogZnVuY3Rpb25zfQoKY29tYmluZV9wZXJtX3N1YmogPSBmdW5jdGlvbihhY2Nfc3Vial9wbG90LCBwZXJtX2FjY19zdWJqKSB7CiAgcGVybV9zdWJqID0gbGlzdCgpCiAgZm9yIChzdWJqIGluIGFjY19zdWJqX3Bsb3QkZGF0YSR4KSB7CiAgICBwZXJtX3N1YmokdmFsW1tzdWJqXV0gPSBzYXBwbHkocGVybV9hY2Nfc3ViaiwgZnVuY3Rpb24ocGVybSkgcGVybVt3aGljaChuYW1lcyhwZXJtKSA9PSBzdWJqKV0pCiAgICBwZXJtX3N1YmokaGlzdFtbc3Vial1dID0gaGlzdF9jaHgocGVybV9zdWJqW1tzdWJqXV0kdmFsLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZShzdWJqLCI6IEFjY3VyYWN5IEFjcm9zcyBcbiIscGVybV90aW1lLCJQZXJtdXRhdGlvbnMiKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKICAgIHBlcm1fc3ViaiRhY2NbW3N1YmpdXSA9IHN1bShwZXJtX3N1YmokdmFsW1tzdWJqXV0pL3Blcm1fdGltZQogIH0KICByZXR1cm4ocGVybV9zdWJqKQp9CgoKc3Vial9zY2F0dGVyX3Blcm0gPSBmdW5jdGlvbihncHNfZGYsYWNjX3N1YmoscGVybV9zdWJqLCBtZXRob2QpewogICAgc3Vial9kZiA8LSBkYXRhLmZyYW1lKHg9dW5pcXVlKGdwc19kZiRJSUQpKQogIHN1YmpfZGYkeSA9IGFjY19zdWJqCiAgc3Vial9kZiR5X3Blcm0gPSB1bmxpc3QocGVybV9zdWJqJGFjYykKICBzdWJqX2RmID0gc3Vial9kZltvcmRlcihzdWJqX2RmJHkpLF0KICBwID0gZ2dwbG90KHN1YmpfZGYsIGFlcyh4ID0gcmVvcmRlcih4LCB5KSwgeSA9IHZhbHVlKSkgKyAKICAgIGdlb21fcG9pbnQoYWVzKHkgPSB5LCBjb2wgPSAic3ViamVjdCBkYXRhIikpICsgCiAgICBnZW9tX3BvaW50KGFlcyh5ID0geV9wZXJtLCBjb2wgPSAicGVybXV0YXRpb24iKSkgKwogICAgdGhlbWVfY293cGxvdCgpICsgCiAgICBsYWJzKHRpdGxlID0gcGFzdGUobWV0aG9kLCJGZWF0dXJlcyBcbiBTdWJqZWN0IExldmVsIEFjY3VyYWN5IEFjcm9zcyIscGFydF90aW1lcywiRGF0YSBQYXJ0aXRpb25zIiksIAogICAgICAgICB4ID0gIlN1YmplY3RzIiwgeSA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IikgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCBzaXplID0gOCksIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQogIHJldHVybihwKQp9CgpgYGAKCgpgYGB7ciBtZWFuIGZlYXR1cmVzIGJ5IHN1YmplY3Qgd2l0aCBwZXJtdXRhdGlvbiwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KcF9zdWJqX21lYW4gPSBzdWJqX3NjYXR0ZXIoZ3BzX2RmX2NsZWFuMiwgYWNjX3N1YmpfbWVhbl9jb3IsICJNZWFuIikKcGVybV9zdWJqX21lYW4gPSBjb21iaW5lX3Blcm1fc3ViaihwX3N1YmpfbWVhbixwZXJtX21lYW5fY29yJHBlcm1fYWNjX3N1YmopCnBfc3Vial9tZWFuX3Blcm0gPSBzdWJqX3NjYXR0ZXJfcGVybShncHNfZGZfY2xlYW4yLCBhY2Nfc3Vial9tZWFuX2NvciwgcGVybV9zdWJqX21lYW4sICJNZWFuIikKZ2dwbG90bHkocF9zdWJqX21lYW5fcGVybSkKYGBgCioqRmlndXJlIDE0OiBTdWJqZWN0IGxldmVsIHByZWRpY3Rpb24gYWNjdXJhY3kgdXNpbmcgbWVhbiBmZWF0dXJlcyBhbmQgbWF0Y2hlZCBieSBtYXggcGVhcnNvbiBjb3JyZWxhdGlvbioqLgoKYGBge3Igcm1zc2QgZmVhdHVyZXMgYnkgc3ViamVjdCB3aXRoIHBlcm11dGF0aW9uLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQpwX3N1Ympfcm1zc2QgPSBzdWJqX3NjYXR0ZXIoZ3BzX2RmX2NsZWFuMiwgYWNjX3N1Ympfcm1zc2RfY29yLCAiUk1TU0QiKQpwZXJtX3N1Ympfcm1zc2QgPSBjb21iaW5lX3Blcm1fc3ViaihwX3N1Ympfcm1zc2QscGVybV9ybXNzZF9jb3IkcGVybV9hY2Nfc3ViaikKcF9zdWJqX3Jtc3NkX3Blcm0gPSBzdWJqX3NjYXR0ZXJfcGVybShncHNfZGZfY2xlYW4yLCBhY2Nfc3Vial9ybXNzZF9jb3IsIHBlcm1fc3Vial9ybXNzZCwgIlJNU1NEIikKZ2dwbG90bHkocF9zdWJqX3Jtc3NkX3Blcm0pCmBgYAoqKkZpZ3VyZSAxNTogU3ViamVjdCBsZXZlbCBwcmVkaWN0aW9uIGFjY3VyYWN5IHVzaW5nIFJNU1NEIGZlYXR1cmVzIGFuZCBtYXRjaGVkIGJ5IG1heCBwZWFyc29uIGNvcnJlbGF0aW9uKiouCgoKIyMjIDEyLiBDb21wYXJlIEZlYXR1cmVzCmBgYHtyIGNvbXBhcmUgYWNyb3NzIGRhdGEgcGFydGl0aW9uLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0xMH0KcF90aW1lX2NvciArIHBfdGltZV9tZWFuICsgcF90aW1lX3Jtc3NkCnFfdGltZV9jb3IgKyBxX3RpbWVfbWVhbiArIHFfdGltZV9ybXNzZApgYGAKKipGaWd1cmUgMTY6IFByZWRpY3Rpb24gYWNjdXJhY3kgYWNyb3NzIHRocmVlIGZlYXR1cmUgc2V0cyoqLgoKYGBge3IgY29tcGFyZSBhY3Jvc3Mgc3ViamVjdHMsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTR9CnBfc3Vial9jb3JfcGVybSAvIHBfc3Vial9tZWFuX3Blcm0gLyBwX3N1Ympfcm1zc2RfcGVybQpgYGAKKipGaWd1cmUgMTc6IFN1YmplY3QgbGV2ZWwgcHJlZGljdGlvbiBhY2N1cmFjeSBhY3Jvc3MgdGhyZWUgZmVhdHVyZSBzZXRzKiouCgoKIyMjIDEzLiBDb25mb3VuZGluZyB2YXJpYWJsZXMKYGBge3Igc3ViaiBhY2N1cmFjeSBjb3Igd2l0aCBwb3RlbnRpYWwgY29uZm91bmRlcnMsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTcsIHdhcm5pbmc9RkFMU0UsIHdtZXNzYWdlPUZBTFNFfQpjb25mX3NjYXR0ZXJfcGxvdCA9IGZ1bmN0aW9uKGNvbmZfdmFyLCBhY2Nfc3ViaiwgY29sKXsKICBpZiAoIWlkZW50aWNhbChjb25mX3ZhciRJSUQsbmFtZXMoYWNjX3N1YmopKSkgewogICAgc3RvcCgic3ViaiBuYW1lcyBkbyBub3QgbWF0Y2giKQogIH0KICBjb25mX2RmID0gZGF0YS5mcmFtZShjb25mID0gdW5saXN0KGNvbmZfdmFyWyxjb2xdKSwgYWNjX3N1YmogPSBhY2Nfc3ViaikKICBjb25mX3NjYXR0ZXIgPSBnZ3NjYXR0ZXIoY29uZl9kZiwgeSA9ICJhY2Nfc3ViaiIsIHggPSAiY29uZiIsCiAgIGFkZCA9ICJyZWcubGluZSIsICAjIEFkZCByZWdyZXNzaW4gbGluZQogICBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibGFjayIsIGZpbGwgPSAibGlnaHRncmF5IiksICMgQ3VzdG9taXplIHJlZy4gbGluZQogICBjb25mLmludCA9IFRSVUUgIyBBZGQgY29uZmlkZW5jZSBpbnRlcnZhbAogICApICsgc3RhdF9jb3IobWV0aG9kID0gInBlYXJzb24iKSArCiAgICB0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgbGFicyh0aXRsZSA9IHBhc3RlKGNvbCksIHkgPSAiR1BTIEZvb3RwcmludCBcbiBQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeCA9IGNvbCkKICByZXR1cm4oY29uZl9zY2F0dGVyKQogIH0KCnN1YmpfbWlubWlzc2luZyA9IGdwc19kZl9jbGVhbjIgJT4lIGdyb3VwX2J5KElJRCkgJT4lIHN1bW1hcmlzZV9pZihpcy5udW1lcmljLCBtZWFuLCBuYS5ybSA9IFRSVUUpCnNwX3Bsb3RzID0gbGlzdCgpCmZvciAoZ3BzX2Z0IGluIGNvbG5hbWVzKHN1YmpfbWlubWlzc2luZylbLTFdKXsKICBzcF9wbG90c1tbZ3BzX2Z0XV0gPSBjb25mX3NjYXR0ZXJfcGxvdChzdWJqX21pbm1pc3NpbmcsYWNjX3N1YmosZ3BzX2Z0KQp9CgpzcF9wbG90c19wdmFscyA9IHNhcHBseShzcF9wbG90cyxmdW5jdGlvbihwbG90KSBjb3IudGVzdChwbG90JGRhdGFbLDFdLHNwX3Bsb3RzJEhvbWV0aW1lJGRhdGFbLDJdKSRwLnZhbHVlKQpzcF9wbG90c19wdmFsc19mZHIgPSBwLmFkanVzdChzcF9wbG90c19wdmFscyxtZXRob2QgPSAiZmRyIikKUmVkdWNlKGArYCwgc3BfcGxvdHMpCmBgYAoqKkZpZ3VyZSAxODogUmVsYXRpb25zaGlwcyBiZXR3ZWVuIHN1YmplY3QgbGV2ZWwgcHJlZGljdGlvbiBhY2N1cmFjeSBhbmQgaW5kaXZpZHVhbCBmZWF0dXJlcyoqLiBOb3RhYmx5LCB0aGVyZSB3YXMgbm8gcmVsYXRpb25zaGlwIGJldHdlZW4gaW5kaXZpZHVhbCBkYXRhIG1pc3NpbmduZXNzIGFuZCBzdWJqZWN0IGxldmVsIHByZWRpY3Rpb24gYWNjdXJhY3kuCgoKYGBge3IgYWNjdXJhY3kgZXhwbGFpbmVkIGJ5IGRhdGEgYW1vdW50fQpzdWJqX2RheXMgPSBncHNfZGZfY2xlYW4yICU+JSBncm91cF9ieShJSUQpICU+JSBkcGx5cjo6dGFsbHkobmFtZSA9ICJEYXlzIENvbGxlY3RlZCIpCmNvbmZfc2NhdHRlcl9wbG90KHN1YmpfZGF5cywgYWNjX3N1YmosICJEYXlzIENvbGxlY3RlZCIpCgojcGVybXV0YXRpb24KZGF5c19hY2NfZGYgPSBkYXRhLmZyYW1lKGFjYyA9IGFjY19zdWJqLCBkYXlzID0gc3Vial9kYXlzJGBEYXlzIENvbGxlY3RlZGApCmRheXNfYWNjX3Blcm1fciA9IGMoKQpmb3IgKGkgaW4gMToxMDAwMDAwKXsKICBhY2Nfc3Vial9wZXJtID0gdmFsdWUoYWNjX3N1Ympbc2FtcGxlKGxlbmd0aChhY2Nfc3ViaikpXSkKICBkYXlzX2FjY19wZXJtX3JbaV0gPSBjb3Ioc3Vial9kYXlzJGBEYXlzIENvbGxlY3RlZGAsYWNjX3N1YmpfcGVybSkKfQoKYGBgCioqRmlndXJlIDE5OiBSZWxhdGlvbnNoaXBzIGJldHdlZW4gc3ViamVjdCBsZXZlbCBwcmVkaWN0aW9uIGFjY3VyYWN5IGFuZCBkYXRhIHF1YW50aXR5KiouIAoKCiMjIyAxNC4gQ29tYmluZSBGZWF0dXJlcwpgYGB7ciBjb21iaW5lIGZlYXR1cmVzfQpncHNfY2xlYW4yX2NvbWJpbmVkX2ZlYXQgPSBsaXN0KCkKZm9yIChzdWJqIGluIG5hbWVzKHN1YmpfbWF0XzEpKXsKICBmb3IgKHRpbWUgaW4gMTpwYXJ0X3RpbWVzKXsKICAgIGNvcl9mZWF0MSA9IGdwc19jbGVhbjJfZmVhdHVyZSRzdWJqX21hdF8xW1tzdWJqXV1bW3RpbWVdXSRjb3IKICAgIG1lYW5fZmVhdDEgPSBncHNfY2xlYW4yX21lYW5fZmVhdCRzdWJqX21hdF8xW1tzdWJqXV1bW3RpbWVdXQogICAgcm1zc2RfZmVhdDEgPSBncHNfY2xlYW4yX3Jtc3NkX2ZlYXQkc3Vial9tYXRfMVtbc3Vial1dW1t0aW1lXV0KICAgIAogICAgY29yX2ZlYXQyID0gZ3BzX2NsZWFuMl9mZWF0dXJlJHN1YmpfbWF0XzJbW3N1YmpdXVtbdGltZV1dJGNvcgogICAgbWVhbl9mZWF0MiA9IGdwc19jbGVhbjJfbWVhbl9mZWF0JHN1YmpfbWF0XzJbW3N1YmpdXVtbdGltZV1dCiAgICBybXNzZF9mZWF0MiA9IGdwc19jbGVhbjJfcm1zc2RfZmVhdCRzdWJqX21hdF8yW1tzdWJqXV1bW3RpbWVdXQogICAgCiAgICBncHNfY2xlYW4yX2NvbWJpbmVkX2ZlYXQkc3Vial9tYXRfMVtbc3Vial1dW1t0aW1lXV0gPSBjKGNvcl9mZWF0MSwgbWVhbl9mZWF0MSwgcm1zc2RfZmVhdDEpCiAgICBncHNfY2xlYW4yX2NvbWJpbmVkX2ZlYXQkc3Vial9tYXRfMltbc3Vial1dW1t0aW1lXV0gPSBjKGNvcl9mZWF0MiwgbWVhbl9mZWF0Miwgcm1zc2RfZmVhdDIpCiAgfQp9CgpgYGAKCmBgYHtyIG1hdGNoIHdpdGggY29tYmluZWQgZmVhdHVyZXMsIGZpZy5hbGlnbj0iY2VudGVyIiwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9M30KbWF0Y2hfY29tYmluZWRfZmVhdCA9IGNhbGNfbWF0Y2hfdmVjdG9yKGdwc19jbGVhbjJfY29tYmluZWRfZmVhdCRzdWJqX21hdF8xLCBncHNfY2xlYW4yX2NvbWJpbmVkX2ZlYXQkc3Vial9tYXRfMiwgImNvciIpCmFjY190aW1lX2NiICA9IGNhbGNfYWNjX3RpbWUobWF0Y2hfY29tYmluZWRfZmVhdCwgIm1heCIpCmFjY19zdWJqX2NiICA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfY29tYmluZWRfZmVhdCwgIm1heCIpCnAgPSBoaXN0X2NoeChhY2NfdGltZV9jYiwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoIkNvbWJpbmVkIEZlYXR1cmVzIFxuIGJ5IGRhdGEgcGFydGl0aW9uIiksIHhheGlzID0gIlByZWRpY3Rpb24gQWNjdXJhY3kiLCB5YXhpcyA9ICJDb3VudCIpCnEgPSBoaXN0X2NoeChhY2Nfc3Vial9jYiwgYmlucyA9IDgsIHRpdGxlID0gcGFzdGUoIkNvbWJpbmVkIEZlYXR1cmVzIFxuIGJ5IHN1YmplY3QiKSwgeGF4aXMgPSAiUHJlZGljdGlvbiBBY2N1cmFjeSIsIHlheGlzID0gIkNvdW50IikKcApgYGAKKipGaWd1cmUgMjA6IFByZWRpY3Rpb24gYWNjdXJhY3kgd2hlbiBhbGwgdGhyZWUgc2V0cyBvZiBmZWF0dXJlcyB3ZXJlIGNvbWJpbmVkKiouIAoKYGBge3IgcnVuIHBlcm11dGF0aW9uIGZvciBjb21iaW5lZCBmZWF0dXJlcywgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnBlcm1fY2JfY29yID0gcGVybV92ZWN0b3IoZ3BzX2RmX2NsZWFuMiwgMTAwMCwgImNvbWJpbmVkIiwgImNvciIpCnBfc3Vial9jYiA9IHN1Ympfc2NhdHRlcihncHNfZGZfY2xlYW4yLCBhY2Nfc3Vial9jYiwgIkNvbWJpbmVkIikKCnBlcm1fc3Vial9jYiA9IGNvbWJpbmVfcGVybV9zdWJqKHBfc3Vial9jYixwZXJtX2NiX2NvciRwZXJtX2FjY19zdWJqKQoKcF90aW1lX21lYW5fcm1zZSA9IGhpc3RfY2h4KGFjY190aW1lX2NiLCBiaW5zID0gOCwgdGl0bGUgPSBwYXN0ZSgiTWVhbiBGZWF0dXJlczogXG4gQXZlcmFnZSBBY2N1cmFjeSBBY3Jvc3MiLHBhcnRfdGltZXMsIkRhdGEgUGFydGl0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQoKcV90aW1lX2NiX2NvciA9ICBoaXN0X2NoeCh1bmxpc3QocGVybV9jYl9jb3IkcGVybV9hY2NfdGltZSksIGJpbnMgPSA4LCB0aXRsZSA9IHBhc3RlKCJBbGwgRmVhdHVyZXM6IFxuIEF2ZXJhZ2UgQWNjdXJhY3kgQWNyb3NzIixsZW5ndGgocGVybV9tZWFuX3Jtc2UkcGVybV9hY2NfdGltZSksIlBlcm11dGF0aW9ucyIpLCB4YXhpcyA9ICJQcmVkaWN0aW9uIEFjY3VyYWN5IiwgeWF4aXMgPSAiQ291bnQiKQoKYGBgCgoKIyMjIDE1LiBBc3NvY2lhdGlvbnMgd2l0aCBQc3ljaG9wYXRob2xvZ3kKYGBge3IgcHN5Y2hvcGF0aG9sb2d5LCBmaWcuYWxpZ249ImNlbnRlciIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmlkcyA9IHJlYWQueGxzeChmaWxlLnBhdGgocHJvamVjdF9wYXRoLCJkYXRhL2NsaW5pY2FsX2RhdGEvc3ViamVjdHRyYWNrZXJfNC54bHN4IikpWzE6bGVuZ3RoKHVuaXF1ZShncHNfZGZfY2xlYW4yJElJRCkpLF0KI3BzeWNoX3Njb3JlID0gcmVhZC5jc3YoZmlsZS5wYXRoKHByb2plY3RfcGF0aCwiZGF0YS9zZWxmX3JlcG9ydF9zY29yZWRfMjAyMDAxMjguY3N2IikpCiMgcHN5Y2hfcHJvID0gcHN5Y2ggJT4lIGZpbHRlcihhcmlfcHJvYmFuZF9jb21wbGV0ZSA9PSAyKQpwc3ljaF9pdGVtID0gcmVhZC5jc3YoZmlsZS5wYXRoKHByb2plY3RfcGF0aCwiZGF0YS9jbGluaWNhbF9kYXRhL3NlbGZfcmVwb3J0X2l0ZW13aXNlLmNzdiIpKQoKcHN5Y2hfYmVpd2UgPSBpbm5lcl9qb2luKGlkcyxwc3ljaF9pdGVtLCBieSA9IGMoIkJCTElEIiA9ICJiYmxpZCIpKQphY2Nfc3Vial9kZiA9IGRhdGEuZnJhbWUoYmVpd2VJRCA9IG5hbWVzKGFjY19zdWJqKSwgYWNjID0gYWNjX3N1YmopCnBzeWNoX2JlaXdlX2FjY19zdWJqID0gaW5uZXJfam9pbihhY2Nfc3Vial9kZixwc3ljaF9iZWl3ZSwgYnkgPSAiYmVpd2VJRCIpCgoKcHN5Y2hfc3VtID0gcHN5Y2hfYmVpd2VfYWNjX3N1YmogJT4lCiAgICAgICAgICAgIG11dGF0ZShzdW1fYWxzID0gcm93U3VtcyguWzI2MzoyODBdLCBuYS5ybSA9IFQpKSAlPiUKICAgICAgICAgICAgbXV0YXRlKHN1bV9hcmkgPSByb3dTdW1zKC5bZ3JlcCgiXmFyaV9bMC05XSQiLGNvbG5hbWVzKHBzeWNoX2JlaXdlX2FjY19zdWJqKSldLCBuYS5ybSA9IFQpKQoKcHN5Y2hfc3VtID0gaW5uZXJfam9pbihwc3ljaF9zdW0sIHN1YmpfZGF5cywgYnkgPSBjKCJiZWl3ZUlEIiA9ICJJSUQiKSkKcHN5Y2hfc3VtJGRheXMgPSBwc3ljaF9zdW0kYERheXMgQ29sbGVjdGVkYApwc3ljaF9zdW0gPSBpbm5lcl9qb2luKHN1YmpfbWlubWlzc2luZywgcHN5Y2hfc3VtLCBieSA9IGMoICJJSUQiID0gImJlaXdlSUQiICkpCnBzeWNoX2FscyA9IGRhdGEuZnJhbWUoYWxzID0gcHN5Y2hfc3VtWywyNjM6MjgwXSwgQkJMSUQgPSBwc3ljaF9zdW0kQkJMSUQpCnBzeWNoX2FyaSA9IGRhdGEuZnJhbWUoYXJpID0gcHN5Y2hfc3VtWywyMzoyOV0sIEJCTElEID0gcHN5Y2hfc3VtJEJCTElEKQogIAogIyBzaG93IHJlc3VsdHMKCmFscy5wY2EgPC0gcHJjb21wKG5hLm9taXQocHN5Y2hfc3VtWywyNjM6MjgwXSksIHNjYWxlID0gVFJVRSkKYXJpLnBjYSA8LSBwcmNvbXAobmEub21pdChwc3ljaF9zdW1bLDIzOjI5XSwgc2NhbGUgPSBUUlVFKSkKCnBzeWNoX3N1bSRwY2FfYWxzID0gYyhhbHMucGNhJHhbMToyMiwiUEMxIl0sIE5BLCBhbHMucGNhJHhbMjM6NDAsIlBDMSJdKQpwc3ljaF9zdW0kcGNhX2FyaSA9IGFyaS5wY2EkeFssIlBDMSJdCnBzeWNoX3N1bSRhbHNfQUQgPSBwc3ljaF9zdW0kYWxzXzIgKyBwc3ljaF9zdW0kYWxzXzEwICsgcHN5Y2hfc3VtJGFsc181ICsgcHN5Y2hfc3VtJGFsc18xMiArIHBzeWNoX3N1bSRhbHNfMTMgKyBwc3ljaF9zdW0kYWxzXzE1ICsgcHN5Y2hfc3VtJGFsc18xNiArIHBzeWNoX3N1bSRhbHNfMTcgKyBwc3ljaF9zdW0kYWxzXzE4CmZ2aXpfZWlnKGFscy5wY2EpIC8gZnZpel9laWcoYXJpLnBjYSkKCmBgYAoKYGBge3IgfQphbHNfYXJpX3N1bSA9IGxtKGFjYyB+IHN1bV9hbHMgKyBzdW1fYXJpICsgZGF5cyAsIGRhdGEgPSBwc3ljaF9zdW0pCgojYWxzX2ZpdCA9IGxtKCBhY2MgfiBzY2FsZShwY2FfYWxzKSArIHNjYWxlKGRheXMpICsgc2NhbGUoYWRtaW5fYWdlKSArIHNjYWxlKGFkbWluX3NleCkgLCBkYXRhID0gcHN5Y2hfc3VtKQojc3VtbWFyeShsbSggYWNjIH4gc2NhbGUoYWxzX0FEKSArIHNjYWxlKGRheXMpICsgc2NhbGUoYWRtaW5fYWdlKSArIHNjYWxlKGFkbWluX3NleCkgLCBkYXRhID0gcHN5Y2hfc3VtKSkkY29lZmZpY2llbnRzWzIsNF0KCiNhcmlfZml0ID0gbG0oIGFjYyB+IHNjYWxlKHBjYV9hcmkpICsgc2NhbGUoZGF5cykgKyBzY2FsZShhZG1pbl9hZ2UpICsgc2NhbGUoYWRtaW5fc2V4KSAsIGRhdGEgPSBwc3ljaF9zdW0pCiNhcmlfYWxzX2ZpdCA9IGxtKCBhY2MgfiBzY2FsZShwY2FfYWxzKSArIHNjYWxlKHBjYV9hcmkpICsgc2NhbGUoZGF5cykgKyBzY2FsZShhZG1pbl9hZ2UpICsgc2NhbGUoYWRtaW5fc2V4KSAsIGRhdGEgPSBwc3ljaF9zdW0pCgpkYXlzX3Bsb3QgPSB2aXNyZWcoYWxzX2FyaV9zdW0sICJkYXlzIiwgZ2cgPSBULCBsaW5lPWxpc3QoY29sPSJibGFjayIpKSArIAogICAgICAgICAgICB5bGFiKCJHUFMgRm9vdHByaW50IFxuIFByZWRpY3Rpb24gQWNjdXJhY3kiKSArIHhsYWIoIkRheXMgQ29sbGVjdGVkIikgKwogICAgICAgICAgICB0aGVtZV9jb3dwbG90KCkgKyBzdGF0X2NvcihtZXRob2QgPSAicGVhcnNvbiIpCnN1bV9hbHNfcGxvdCA9IHZpc3JlZyhhbHNfYXJpX3N1bSwgInN1bV9hbHMiLCBnZyA9IFQsICBsaW5lPWxpc3QoY29sPSJibGFjayIpKSArIAogICAgICAgICAgICB5bGFiKCJHUFMgRm9vdHByaW50IFxuIFByZWRpY3Rpb24gQWNjdXJhY3kiKSArIHhsYWIoIkFmZmVjdGl2ZSBMYWJpbGl0eSBTY29yZSAoQUxTKSIpICsKICAgICAgICAgICAgdGhlbWVfY293cGxvdCgpICsgc3RhdF9jb3IobWV0aG9kID0gInBlYXJzb24iKQpzdW1fYXJpX3Bsb3QgPSB2aXNyZWcoYWxzX2FyaV9zdW0sICJzdW1fYXJpIiwgZ2cgPSBULCBsaW5lPWxpc3QoY29sPSJibGFjayIpKSArIAogICAgICAgICAgICB5bGFiKCJHUFMgRm9vdHByaW50IFxuIFByZWRpY3Rpb24gQWNjdXJhY3kiKSArIHhsYWIoIkFmZmVjdGl2ZSBSZWFjdGl2aXR5IEluZGV4IChBUkkpIikgKwogICAgICAgICAgICB0aGVtZV9jb3dwbG90KCkgKyBzdGF0X2NvcihtZXRob2QgPSAicGVhcnNvbiIpCmRheXNfcGxvdCAvIHN1bV9hbHNfcGxvdCAvIHN1bV9hcmlfcGxvdApgYGAKCmBgYHtyIHBzeWNob3BhdGhvbG9neSBwZXJtdXRhdGlvbn0KI3Blcm11dGF0aW9uCmFsc19hcmlfcGVybV9yID0gYygpCmZvciAoaSBpbiAxOjEwMDApewogIHBzeWNoX3N1bV9wZXJtID0gcHN5Y2hfc3VtCiAgcHN5Y2hfc3VtX3Blcm0kYWxzX3Blcm0gPSBwc3ljaF9zdW0kc3VtX2Fsc1tzYW1wbGUobGVuZ3RoKHBzeWNoX3N1bSRzdW1fYWxzKSldCiAgYWxzX2FyaV9wZXJtX3JbaV0gPSBzdW1tYXJ5KGxtKGFjYyB+IGFsc19wZXJtICsgc3VtX2FyaSArIGRheXMgLCBkYXRhID0gcHN5Y2hfc3VtX3Blcm0pKVtbOF1dCn0KCmBgYAoKYGBge3IgfQptb2QgPC0gbG0oYWNjfnN1bV9hbHMqc3VtX2FyaSArIGRheXMgLCBkYXRhPXBzeWNoX3N1bSkgICMganVzdCByYXcgc2NvcmVzLCBubyByZXNpZHVhbHMKYWxzX2ludF9wbG90IDwtIHZpc3JlZyhtb2QseHZhcj0ic3VtX2FscyIsYnk9InN1bV9hcmkiLG92ZXJsYXk9VFJVRSxzdHJpcC5uYW1lcz1UUlVFKQp2aXNyZWcobW9kLHh2YXI9InN1bV9hcmkiLGJ5PSJzdW1fYWxzIixvdmVybGF5PVRSVUUpCmBgYAoqKkZpZ3VyZSAyMTogQXNzb2NpYXRpb25zIGJldHdlZW4gaXJyaXRhYmlsaXR5IGFuZCBwcmVkaWN0aW9uIGFjY3VyYWN5KiouIAoKIyMjIDE2LiBBc3NvY2lhdGlvbnMgd2l0aCBDb2duaXRpb24KYGBge3IgQ29nbml0aW9uLCBmaWcuYWxpZ249ImNlbnRlciIsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNuYl9zY29yZSA9IHJlYWQuY3N2KGZpbGUucGF0aChwcm9qZWN0X3BhdGgsImRhdGEvZ3JtcHlfYmlmYWN0b3JfY25iX2NsZWFuZWQuY3N2IikpCmNvcl90cmFpdHMgPSByZWFkLmNzdihmaWxlLnBhdGgocHJvamVjdF9wYXRoLCJkYXRhL2dybXB5X2NvcnJ0cmFpdHNfY25iX2NsZWFuZWQuY3N2IikpCmNuYl9iZWl3ZSA9IGlubmVyX2pvaW4oaWRzLGNuYl9zY29yZSwgYnkgPSBjKCJCQkxJRCIgPSAiYmJsaWQiKSkKCmBgYAoKIyMjIDE3LlNpbWlsYXJpdHkgTWF0cml4IENvcnJlbGF0aW9uCmBgYHtyfQpncHNfd2lkZV9tYXRyaXhfYWxsID0gZGF0YS5mcmFtZSgpCgpzdWJqX3NlcV9tYXQgPSBhcnJhbmdlKHN1YmpfZGF5cyxgRGF5cyBDb2xsZWN0ZWRgKSRJSUQKCmZvciAoc3ViaiBpbiBzdWJqX3NlcV9tYXQpewogICNpZiAoKHN1YmogJWluJSBzdWJqX2RheXMkSUlEW3doaWNoKHN1YmpfZGF5cyRgRGF5cyBDb2xsZWN0ZWRgPD0xMCldKSA9PSBUKSB7CiAgICBmb3IgKHBhcnQgaW4gMToxKXsKICAgICAgaGFsZl8xID0gZ3BzX2NsZWFuMl9mZWF0dXJlJHN1YmpfbWF0XzFbW3N1YmpdXVtbcGFydF1dJGNvcgogICAgICBoYWxmXzIgPSBncHNfY2xlYW4yX2ZlYXR1cmUkc3Vial9tYXRfMltbc3Vial1dW1twYXJ0XV0kY29yCiAgICAgIGhhbGZfMV9oYWxmXzIgPSBjKGhhbGZfMSxoYWxmXzIpCiAgICAgIGdwc193aWRlX21hdHJpeF9hbGwgPSByYmluZChncHNfd2lkZV9tYXRyaXhfYWxsLGhhbGZfMV9oYWxmXzIpCiAgICAgIH0KICAgICN9Cn0KCmdwc193aWRlX21hdHJpeF9hbGwgPSB0KGdwc193aWRlX21hdHJpeF9hbGwpCmdwc19jb3JwbG90X2FsbCA9IHJxdWVyeS5jb3JtYXQoZ3BzX3dpZGVfbWF0cml4X2FsbCwgdHlwZSA9ICJmdWxsIixncmFwaD1GQUxTRSkKZ3BzX2NvcnBsb3RfYWxsJHN1YmogPSBhcnJhbmdlKHN1YmpfZGF5cyxgRGF5cyBDb2xsZWN0ZWRgKSRJSUQKCmxldmVscGxvdChncHNfY29ycGxvdF9hbGwkcixzY2FsZXM9bGlzdChkcmF3PUZBTFNFKSxjb2wucmVnaW9ucyA9IHJldihyYWluYm93KDEwMDApKVstYygxOjIwKV0sIHJlZ2lvbiA9VCwgeWxhYi5yaWdodCA9ICJQZWFyc29uIGNvcnJlbGF0aW9uIiwgbWFpbj1saXN0KGxhYmVsPSdHUFMgRmVhdHVyZSBTaW1pbGFyaXR5JykseGxhYj0iU3ViamVjdHMiLHlsYWI9IlN1YmplY3RzIikKYGBgCgpgYGB7cn0Kbl9zdWJqID0gbGVuZ3RoKHN1Ympfc2VxX21hdCkKZ3BzX2RheXNfc2ltX21hdHJpeCA9IG1hdHJpeChOQSwgbl9zdWJqLG5fc3ViaikKZm9yIChpIGluIDE6bl9zdWJqKXsKICBmb3IgKGogaW4gMTpuX3N1YmopewogICAgc3Vial9pX2RheXMgPSBzdWJqX2RheXMkYERheXMgQ29sbGVjdGVkYFt3aGljaChzdWJqX2RheXMkSUlEID09IHN1Ympfc2VxX21hdFtpXSldCiAgICBzdWJqX2pfZGF5cyA9IHN1YmpfZGF5cyRgRGF5cyBDb2xsZWN0ZWRgW3doaWNoKHN1YmpfZGF5cyRJSUQgPT0gc3Vial9zZXFfbWF0W2pdKV0KICAgIGdwc19kYXlzX3NpbV9tYXRyaXhbaSxqXSA9IGFicyhzdWJqX2lfZGF5cy1zdWJqX2pfZGF5cykvbWF4KHN1YmpfZGF5cyRgRGF5cyBDb2xsZWN0ZWRgKQogIH0KfQpsZXZlbHBsb3QoZ3BzX2RheXNfc2ltX21hdHJpeCxzY2FsZXM9bGlzdChkcmF3PUZBTFNFKSxjb2wucmVnaW9ucyA9IHJldihyYWluYm93KDEwMDApKVstYygxOjIwKV0sIHJlZ2lvbiA9VCwgeWxhYi5yaWdodCA9ICJEaWZmZXJlbmNlcyBpbiBEYXlzIENvbGxlY3RlZCIsIG1haW49bGlzdChsYWJlbD0nRGF5cyBDb2xsZWN0ZWQgU2ltaWxhcml0eScpLHhsYWI9IlN1YmplY3RzIix5bGFiPSJTdWJqZWN0cyIpCgpgYGAKCmBgYHtyfQpuX3N1YmogPSBsZW5ndGgoc3Vial9zZXFfbWF0KQpncHNfbWluc19zaW1fbWF0cml4ID0gbWF0cml4KE5BLCBuX3N1Ymosbl9zdWJqKQpmb3IgKGkgaW4gMTpuX3N1YmopewogIGZvciAoaiBpbiAxOm5fc3Viail7CiAgICBzdWJqX2lfbWlucyA9IHN1YmpfbWlubWlzc2luZyRNaW5zTWlzc2luZ1t3aGljaChzdWJqX21pbm1pc3NpbmckSUlEID09IHN1Ympfc2VxX21hdFtpXSldCiAgICBzdWJqX2pfbWlucyA9IHN1YmpfbWlubWlzc2luZyRNaW5zTWlzc2luZ1t3aGljaChzdWJqX21pbm1pc3NpbmckSUlEID09IHN1Ympfc2VxX21hdFtqXSldCiAgICBncHNfbWluc19zaW1fbWF0cml4W2ksal0gPSBhYnMoc3Vial9pX21pbnMtc3Vial9qX21pbnMpL21heChzdWJqX21pbm1pc3NpbmckTWluc01pc3NpbmcpCiAgfQp9CmxldmVscGxvdChncHNfbWluc19zaW1fbWF0cml4LHNjYWxlcz1saXN0KGRyYXc9RkFMU0UpLGNvbC5yZWdpb25zID0gcmV2KHJhaW5ib3coMTAwMCkpWy1jKDE6MjApXSwgcmVnaW9uID1ULCB5bGFiLnJpZ2h0ID0gIkRpZmZlcmVuY2VzIGluIERheXMgQ29sbGVjdGVkIiwgbWFpbj1saXN0KGxhYmVsPSdNaW5zIE1pc3NpbmcgU2ltaWxhcml0eScpLHhsYWI9IlN1YmplY3RzIix5bGFiPSJTdWJqZWN0cyIpCgpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9Cmdwc19zaW1fZGYgPSBkYXRhLmZyYW1lKGdwcyA9IGdwc19jb3JwbG90X2FsbCRyW2xvd2VyLnRyaShncHNfY29ycGxvdF9hbGwkcildLCBkYXlzID0gZ3BzX2RheXNfc2ltX21hdHJpeFtsb3dlci50cmkoZ3BzX2RheXNfc2ltX21hdHJpeCldLCBtaW5zID0gZ3BzX21pbnNfc2ltX21hdHJpeFtsb3dlci50cmkoZ3BzX21pbnNfc2ltX21hdHJpeCldKQoKZGF5c19zaW1fcGxvdCA9IGdnc2NhdHRlcihncHNfc2ltX2RmLCB5ID0gImdwcyIsIHggPSAiZGF5cyIsCiAgIGFkZCA9ICJyZWcubGluZSIsICAjIEFkZCByZWdyZXNzaW4gbGluZQogICBhZGQucGFyYW1zID0gbGlzdChjb2xvciA9ICJibGFjayIsIGZpbGwgPSAibGlnaHRncmF5IiksICMgQ3VzdG9taXplIHJlZy4gbGluZQogICBjb25mLmludCA9IFRSVUUgIyBBZGQgY29uZmlkZW5jZSBpbnRlcnZhbAogICApICsgc3RhdF9jb3IobWV0aG9kID0gInBlYXJzb24iKSArCiAgICB0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgbGFicyh5ID0gIkdQUyBTaW1pbGFyaXR5IiwgeCA9ICJEYXlzIENvbGxlY3RlZCBTaW1pbGFyaXR5IiwgdGl0bGUgPSAiU2ltaWxhcml0eSBDb3JyZWxhdGlvbiIpCgptaW5zX3NpbV9wbG90ID0gZ2dzY2F0dGVyKGdwc19zaW1fZGYsIHkgPSAiZ3BzIiwgeCA9ICJtaW5zIiwKICAgYWRkID0gInJlZy5saW5lIiwgICMgQWRkIHJlZ3Jlc3NpbiBsaW5lCiAgIGFkZC5wYXJhbXMgPSBsaXN0KGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJsaWdodGdyYXkiKSwgIyBDdXN0b21pemUgcmVnLiBsaW5lCiAgIGNvbmYuaW50ID0gVFJVRSAjIEFkZCBjb25maWRlbmNlIGludGVydmFsCiAgICkgKyBzdGF0X2NvcihtZXRob2QgPSAicGVhcnNvbiIpICsKICAgIHRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgICBsYWJzKHkgPSAiR1BTIFNpbWlsYXJpdHkiLCB4ID0gIk1pbnMgTWlzc2luZyBTaW1pbGFyaXR5IiwgdGl0bGUgPSAiU2ltaWxhcml0eSBDb3JyZWxhdGlvbiIpCgpkYXlzX3NpbV9wbG90IC8gbWluc19zaW1fcGxvdApgYGAKCiMjIyAxOC5HUFMgU2NvcmUKYGBge3J9Cmdwc19tZWFuID0gZ3BzX2RmX2NsZWFuMiAlPiUgZ3JvdXBfYnkoSUlEKSAlPiUgc3VtbWFyaXNlX2lmKGlzLm51bWVyaWMsIG1lYW4sIG5hLnJtID0gVFJVRSkKcmVzLnBjYSA8LSBwcmNvbXAoZ3BzX21lYW5bLDI6MTZdLCBzY2FsZSA9IFRSVUUpCmZ2aXpfZWlnKHJlcy5wY2EpCmdwc19zY29yZSA9IHJlcy5wY2EkeFssJ1BDMSddCmdwc19zY29yZV9kZiA9IGRhdGEuZnJhbWUoYmVpd2VJRCA9IGdwc19tZWFuJElJRCwgZ3BzX3Njb3JlID0gZ3BzX3Njb3JlKQpmY19jb21fbWVhbl8xID0gYXMuZGF0YS5mcmFtZShzYXBwbHkoZmNfY29tXzEsIGZ1bmN0aW9uKGNvbSkgc2FwcGx5KGNvbSwgZnVuY3Rpb24oc3ViaikgbWVhbihzdWJqJFYxKSkpKQpmY19jb21fbWVhbl8xJEJCTElEID0gYXMubnVtZXJpYyhyb3duYW1lcyhmY19jb21fbWVhbl8xKSkKZmNfY29tX21lYW5fMSA9IGlubmVyX2pvaW4oaWRzWyxjKDEsMyldLGZjX2NvbV9tZWFuXzEsIGJ5ID0gIkJCTElEIiApCmZjX2NvbV9tZWFuXzFfZ3BzID0gaW5uZXJfam9pbihmY19jb21fbWVhbl8xLGdwc19zY29yZV9kZiwgYnkgPSAiYmVpd2VJRCIpCgoKZnZpel9wY2FfaW5kKHJlcy5wY2EsCiAgICAgICAgICAgICBjb2wuaW5kID0gImNvczIiLCAjIENvbG9yIGJ5IHRoZSBxdWFsaXR5IG9mIHJlcHJlc2VudGF0aW9uCiAgICAgICAgICAgICBncmFkaWVudC5jb2xzID0gYygiIzAwQUZCQiIsICIjRTdCODAwIiwgIiNGQzRFMDciKSwKICAgICAgICAgICAgIHJlcGVsID0gVFJVRSAgICAgIyBBdm9pZCB0ZXh0IG92ZXJsYXBwaW5nCiAgICAgICAgICAgICApCgpmdml6X3BjYV92YXIocmVzLnBjYSwKICAgICAgICAgICAgIGNvbC52YXIgPSAiY29udHJpYiIsICMgQ29sb3IgYnkgY29udHJpYnV0aW9ucyB0byB0aGUgUEMKICAgICAgICAgICAgIGdyYWRpZW50LmNvbHMgPSBjKCIjMDBBRkJCIiwgIiNFN0I4MDAiLCAiI0ZDNEUwNyIpLAogICAgICAgICAgICAgcmVwZWwgPSBUUlVFICAgICAjIEF2b2lkIHRleHQgb3ZlcmxhcHBpbmcKICAgICAgICAgICAgICkKCmZ2aXpfcGNhX2JpcGxvdChyZXMucGNhLCByZXBlbCA9IFRSVUUsCiAgICAgICAgICAgICAgICBjb2wudmFyID0gIiMyRTlGREYiLCAjIFZhcmlhYmxlcyBjb2xvcgogICAgICAgICAgICAgICAgY29sLmluZCA9ICIjNjk2OTY5IiAgIyBJbmRpdmlkdWFscyBjb2xvcgogICAgICAgICAgICAgICAgKQpgYGAKCiMjIyAxOS5GZWF0dXJlIExlaXNvbgoKYGBge3IgZmVhdHVyZSBsZWlzb24gZXhwZXJpbWVudCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZmVhdF9uYW1lcyA9IG5hbWVzKGdwc19kZl9jbGVhbjJbLDM6MTddKQpzdWJqX3NlcV8xMDAgPSBtYWtlX3N1Ympfc2VxKGdwc19kZl9jbGVhbjIsIDEwMCkKZ3BzX2Z0X2xlaXNvbiA9IGxpc3QoKQpncHNfZGZfcGVybSA9IGdwc19kZl9jbGVhbjIKcGVybV90aW1lX2N1dCA9IDEwMApwZXJtX2FjY190aW1lX2N1dCA9IGxpc3QoKQpwZXJtX2FjY19zdWJqX2N1dCA9IGxpc3QoKQpmb3IgKGkgaW4gMToxNSl7CiAgZnRfcm12ID0gZmVhdF9uYW1lc1tpXQogIHByaW50KGZ0X3JtdikKICByYW5nZSA9IGMoMzoxNylbLWldCiAgcHJpbnQocmFuZ2UpCiAgIyBvcmlnaW5hbCBkYXRhCiAgZmVhdHVyZV9uZCA9IG1ha2VfZmVhdHVyZV9tYXRyaXgoZ3BzX2RmX2NsZWFuMixzdWJqX3NlcV8xMDAscmFuZ2UpCiAgbWF0Y2hfY29yX25kID0gY2FsY19tYXRjaF9jb3IoZmVhdHVyZV9uZCRzdWJqX21hdF8xLGZlYXR1cmVfbmQkc3Vial9tYXRfMikKICBncHNfZnRfbGVpc29uW1tmdF9ybXZdXSRhY2NfdGltZSA9IGNhbGNfYWNjX3RpbWUobWF0Y2hfY29yX25kLCAibWF4IikKICBncHNfZnRfbGVpc29uW1tmdF9ybXZdXSRhY2Nfc3ViaiA9IGNhbGNfYWNjX3N1YmoobWF0Y2hfY29yX25kLCAibWF4IikKICBwcmludChtZWFuKGdwc19mdF9sZWlzb25bW2Z0X3Jtdl1dJGFjY190aW1lKSkKfQogICNwZXJtdXRhdGlvbiBkYXRhCmZvciAoaSBpbiAxOjE1KXsKICBmdF9ybXYgPSBmZWF0X25hbWVzW2ldCiAgcHJpbnQoZnRfcm12KQogIHJhbmdlID0gYygzOjE3KVstaV0KICBwcmludChyYW5nZSkKICBmb3IgKGogaW4gMTpwZXJtX3RpbWVfY3V0KSB7CiAgICBwZXJtX3BhcnRfdGltZXMgPSAxCiAgICBwcmludChwYXN0ZSgicGVybXV0aW5nIC4uLiIsIGksIi4uLiIpKQogICAgZ3BzX2RmX3Blcm0kSUlEID0gc2FtcGxlKGdwc19kZl9wZXJtJElJRCkKICAgIHBlcm1fc3Vial9zZXEgPSBtYWtlX3N1Ympfc2VxKGdwc19kZl9wZXJtLCBwYXJ0X3RpbWVzID0gcGVybV9wYXJ0X3RpbWVzKQogICAgcGVybV9ncHMgPSBtYWtlX2ZlYXR1cmVfbWF0cml4KGdwc19kZl9wZXJtLHBlcm1fc3Vial9zZXEscmFuZ2UpCiAgICBwZXJtX21hdF8xID0gcGVybV9ncHMkc3Vial9tYXRfMQogICAgcGVybV9tYXRfMiA9IHBlcm1fZ3BzJHN1YmpfbWF0XzIKICAgIHBlcm1fbWF0Y2hfY29yID0gY2FsY19tYXRjaF9jb3IocGVybV9tYXRfMSxwZXJtX21hdF8yKQogICAgcGVybV9hY2NfdGltZV9jdXRbW2Z0X3Jtdl1dW1tqXV0gPSBjYWxjX2FjY190aW1lKHBlcm1fbWF0Y2hfY29yKQogICAgcGVybV9hY2Nfc3Vial9jdXRbW2Z0X3Jtdl1dW1tqXV0gPSBjYWxjX2FjY19zdWJqKHBlcm1fbWF0Y2hfY29yKQogIH0KICBwcmludChtZWFuKHVubGlzdChwZXJtX2FjY190aW1lX2N1dFtbZnRfcm12XV0pKSkKfQpgYGAKCmBgYHtyIGdwc19sZXNpb25fZGZ9CmFjY190aW1lX2xlc2lvbl9kZiA9IGFzLmRhdGEuZnJhbWUoc2FwcGx5KGdwc19mdF9sZWlzb24sIGZ1bmN0aW9uKGxlc2lvbikgbGVzaW9uJGFjY190aW1lKSkKYWNjX3RpbWVfbGVzaW9uX2RmJGBOb25lX1JlbW92ZWRgID0gYWNjX3RpbWVbMToxMF0KYWNjX3RpbWVfbGVzaW9uX2RmIDwtIGFzLmRhdGEuZnJhbWUoZ2F0aGVyKGFjY190aW1lX2xlc2lvbl9kZiwgZmVhdHVyZSwgYWNjX3RpbWUsIGNvbG5hbWVzKGFjY190aW1lX2xlc2lvbl9kZiksIGZhY3Rvcl9rZXk9VFJVRSkpCgpzdGF0LnRlc3QgPC0gYWNjX3RpbWVfbGVzaW9uX2RmICU+JSByc3RhdGl4Ojp0X3Rlc3QoYWNjX3RpbWUgfiBmZWF0dXJlLCByZWYuZ3JvdXAgPSAiTm9uZV9SZW1vdmVkIiwgcC5hZGp1c3QubWV0aG9kID0gImJvbmZlcnJvbmkiKQoKcDwtZ2dwbG90KGFjY190aW1lX2xlc2lvbl9kZiwgYWVzKHg9cmVvcmRlcihmZWF0dXJlLCBhY2NfdGltZSwgRlVOID0gbWVkaWFuKSwgeT1hY2NfdGltZSkpICsgCiAgIGdlb21faml0dGVyKGFlcyhjb2xvdXIgPSBmZWF0dXJlKSwgc2hvdy5sZWdlbmQgPSBGLCB3aWR0aCA9IDAuMTApICsgCiAgZ2VvbV9ib3hwbG90KHNob3cubGVnZW5kID0gRiwgYWxwaGEgPSAwLjIsIG91dGxpZXIuYWxwaGEgPSAwKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMSkpICsKICB0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSkpICsKICAgIHlsYWIoIkdQUyBGb290cHJpbnQgUHJlZGljaXRvbiBBY2N1cmFjeSIpICsgeGxhYigiRmVhdHVyZSBSZW1vdmVkIikgIysgCiAgIyBnZ3B1YnI6OnN0YXRfcHZhbHVlX21hbnVhbCgKICAjICAgc3RhdC50ZXN0LCBsYWJlbCA9ICJwLmFkaiIsIAogICMgICB5LnBvc2l0aW9uID0gMC44LAogICMgICByZW1vdmUuYnJhY2tldCA9IEYsCiAgIyAgIHN0ZXAuaW5jcmVhc2UgPSAwLjEsCiAgIyAgIGhpZGUubnMgPSBUCiAgIyAgICkKcAoKYGBgCgpgYGB7ciBncHNfbGVzaW9uX3Blcm19CmFjY190aW1lX2xlc2lvbl9wZXJtX2RmID0gYXMuZGF0YS5mcmFtZShzYXBwbHkocGVybV9hY2NfdGltZV9jdXQsIGZ1bmN0aW9uKGxlc2lvbikgdW5saXN0KGxlc2lvbikpKQphY2NfdGltZV9sZXNpb25fcGVybV9kZiRgTm9uZV9SZW1vdmVkYCA9IHBlcm1fYWNjX3RpbWVfYWxsWzE6MTBdCmFjY190aW1lX2xlc2lvbl9wZXJtX2RmIDwtIGFzLmRhdGEuZnJhbWUoZ2F0aGVyKGFjY190aW1lX2xlc2lvbl9wZXJtX2RmLCBmZWF0dXJlLCBhY2NfdGltZSwgY29sbmFtZXMoYWNjX3RpbWVfbGVzaW9uX3Blcm1fZGYpLCBmYWN0b3Jfa2V5PVRSVUUpKQoKc3RhdC50ZXN0LnBlcm0gPC0gYWNjX3RpbWVfbGVzaW9uX3Blcm1fZGYgJT4lIHJzdGF0aXg6OnRfdGVzdChhY2NfdGltZSB+IGZlYXR1cmUsIHJlZi5ncm91cCA9ICJOb25lX1JlbW92ZWQiLCBwLmFkanVzdC5tZXRob2QgPSAiYm9uZmVycm9uaSIpCgpwX3Blcm08LWdncGxvdChhY2NfdGltZV9sZXNpb25fcGVybV9kZiwgYWVzKHg9cmVvcmRlcihmZWF0dXJlLCBhY2NfdGltZSwgRlVOID0gbWVkaWFuKSwgeT1hY2NfdGltZSkpICsgCiAgIGdlb21faml0dGVyKGFlcyhjb2xvdXIgPSBmZWF0dXJlKSwgc2hvdy5sZWdlbmQgPSBGLCB3aWR0aCA9IDAuMTApICsgCiAgZ2VvbV9ib3hwbG90KHNob3cubGVnZW5kID0gRiwgYWxwaGEgPSAwLjIsIG91dGxpZXIuYWxwaGEgPSAwKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMSkpICsKICB0aGVtZV9jb3dwbG90KCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSkpICsKICAgIHlsYWIoIkdQUyBGb290cHJpbnQgUHJlZGljaXRvbiBBY2N1cmFjeSIpICsgeGxhYigiRmVhdHVyZSBSZW1vdmVkIikgIysgCiAgIyBnZ3B1YnI6OnN0YXRfcHZhbHVlX21hbnVhbCgKICAjICAgc3RhdC50ZXN0LCBsYWJlbCA9ICJwLmFkaiIsIAogICMgICB5LnBvc2l0aW9uID0gMC44LAogICMgICByZW1vdmUuYnJhY2tldCA9IEYsCiAgIyAgIHN0ZXAuaW5jcmVhc2UgPSAwLjEsCiAgIyAgIGhpZGUubnMgPSBUCiAgIyAgICkKcF9wZXJtCmBgYAoKCmBgYHtyfQpzdWJqX21pbnMgPSBncHNfZGZfY2xlYW4yICU+JSBncm91cF9ieShJSUQpICU+JSBkcGx5cjo6dGFsbHkobmFtZSA9ICJEYXlzIENvbGxlY3RlZCIpCmNvbmZfc2NhdHRlcl9wbG90KHN1YmpfbWlubWlzc2luZywgZ3BzX2Z0X2xlaXNvbiRNaW5zTWlzc2luZyRhY2Nfc3ViaiwgIk1pbnNNaXNzaW5nIikKYGBgCgoKIyMjIDE5LiBNaXNjIEluZm8KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KI3Nlc3Npb25JbmZvKCkKYGBgCgo=
 

A work by Cedric Huchuan Xia

hxia@upenn.edu